/* See LICENSE file for copyright and license details.
 *
 * dynamic window manager is designed like any other X client as well. It is
 * driven through handling X events. In contrast to other X clients, a window
 * manager selects for SubstructureRedirectMask on the root window, to receive
 * events about window (dis-)appearance. Only one X connection at a time is
 * allowed to select for this event mask.
 *
 * The event handlers of dwm are organized in an array which is accessed
 * whenever a new event has been fetched. This allows event dispatching
 * in O(1) time.
 *
 * Each child of the root window is called a client, except windows which have
 * set the override_redirect flag. Clients are organized in a linked client
 * list on each monitor, the focus history is remembered through a stack list
 * on each monitor. Each client contains a bit array to indicate the tags of a
 * client.
 *
 * Keys and tagging rules are organized as arrays and defined in config.h.
 *
 * To understand everything else, start reading main().
 */
#include <time.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <locale.h>
#include <signal.h>
#include <stdarg.h>
#include <sys/wait.h>
#include <X11/Xlib.h>
#include <sys/types.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/Xproto.h>
#include <X11/cursorfont.h>
#ifdef XINERAMA
#include <X11/extensions/Xinerama.h>
#endif /* XINERAMA */
#include <sys/procfs.h>
#include <X11/Xft/Xft.h>
#include <X11/extensions/Xfixes.h>

#include "log.h"
#include "drw.h"
#include "util.h"
#include "macros.h"
#include "patches.h"

#if BAR_FLEXWINTITLE_PATCH
#ifndef FLEXTILE_DELUXE_LAYOUT
#define FLEXTILE_DELUXE_LAYOUT 1
#endif
#endif

#include <pango/pango.h>

#if XKB_PATCH
#include <X11/XKBlib.h>
#endif // XKB_PATCH

#if SPAWNCMD_PATCH
#include <assert.h>
#include <libgen.h>
#include <sys/stat.h>
#define SPAWN_CWD_DELIM " []{}()<>\"':"
#endif // SPAWNCMD_PATCH

/* macros */
#define Button6                 6
#define Button7                 7
#define Button8                 8
#define Button9                 9
#define NUMTAGS                 9
#define NUMVIEWHIST             NUMTAGS
#define BARRULES                20
#define BUTTONMASK              (ButtonPressMask|ButtonReleaseMask)
#define CLEANMASK(mask)         (mask & ~(numlockmask|LockMask) & (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask))

#define INTERSECT(x,y,w,h,m)    (MAX(0, MIN((x)+(w),(m)->wx+(m)->ww) - MAX((x),(m)->wx)) \
                               * MAX(0, MIN((y)+(h),(m)->wy+(m)->wh) - MAX((y),(m)->wy)))
#if ATTACHASIDE_PATCH && STICKY_PATCH
#define ISVISIBLEONTAG(C, T)    ((C->tags & T) || C->issticky)
#define ISVISIBLE(C)            ISVISIBLEONTAG(C, C->mon->tagset[C->mon->seltags])
#elif ATTACHASIDE_PATCH
#define ISVISIBLEONTAG(C, T)    ((C->tags & T))
#define ISVISIBLE(C)            ISVISIBLEONTAG(C, C->mon->tagset[C->mon->seltags])
#elif STICKY_PATCH
#define ISVISIBLE(C)            ((C->tags & C->mon->tagset[C->mon->seltags]) || C->issticky)
#else
#define ISVISIBLE(C)            ((C->tags & C->mon->tagset[C->mon->seltags]))
#endif // ATTACHASIDE_PATCH
#define LENGTH(X)               (sizeof X / sizeof X[0])
#define MOUSEMASK               (BUTTONMASK|PointerMotionMask)
#define WIDTH(X)                ((X)->w + 2 * (X)->bw)
#define HEIGHT(X)               ((X)->h + 2 * (X)->bw)
#define WTYPE                   "_NET_WM_WINDOW_TYPE_"
#if SCRATCHPADS_PATCH && !RENAMED_SCRATCHPADS_PATCH
#define TOTALTAGS               (NUMTAGS + LENGTH(scratchpads))
#define TAGMASK                 ((1 << TOTALTAGS) - 1)
#define SPTAG(i)                ((1 << NUMTAGS) << (i))
#define SPTAGMASK               (((1 << LENGTH(scratchpads))-1) << NUMTAGS)
#else
#define TAGMASK                 ((1 << NUMTAGS) - 1)
#endif // SCRATCHPADS_PATCH
#define TEXTWM(X)               (drw_fontset_getwidth(drw, (X), True) + lrpad)
#define TEXTW(X)                (drw_fontset_getwidth(drw, (X), False) + lrpad)
#define HIDDEN(C)               ((getstate(C->win) == IconicState))

/* enums */
enum {
    #if RESIZEPOINT_PATCH || RESIZECORNERS_PATCH
    CurResizeBR,
    CurResizeBL,
    CurResizeTR,
    CurResizeTL,
    #endif // RESIZEPOINT_PATCH | RESIZECORNERS_PATCH
    CurResizeHorzArrow,
    CurResizeVertArrow,
    #if DRAGCFACT_PATCH
    CurIronCross,
    #endif // DRAGCFACT_PATCH
    CurNormal,
    CurResize,
    CurMove,
    CurLast
}; /* cursor */

enum {
    SchemeNorm,
    SchemeSel,
    SchemeTitleNorm,
    SchemeTitleSel,
    SchemeTagsNorm,
    SchemeTagsSel,
    SchemeHidNorm,
    SchemeHidSel,
    SchemeUrg,
    #if RENAMED_SCRATCHPADS_PATCH
    SchemeScratchSel,
    SchemeScratchNorm,
    #endif // RENAMED_SCRATCHPADS_PATCH
    #if BAR_FLEXWINTITLE_PATCH
    SchemeFlexActTTB,
    SchemeFlexActLTR,
    SchemeFlexActMONO,
    SchemeFlexActGRID,
    SchemeFlexActGRD1,
    SchemeFlexActGRD2,
    SchemeFlexActGRDM,
    SchemeFlexActHGRD,
    SchemeFlexActDWDL,
    SchemeFlexActSPRL,
    SchemeFlexInaTTB,
    SchemeFlexInaLTR,
    SchemeFlexInaMONO,
    SchemeFlexInaGRID,
    SchemeFlexInaGRD1,
    SchemeFlexInaGRD2,
    SchemeFlexInaGRDM,
    SchemeFlexInaHGRD,
    SchemeFlexInaDWDL,
    SchemeFlexInaSPRL,
    SchemeFlexSelTTB,
    SchemeFlexSelLTR,
    SchemeFlexSelMONO,
    SchemeFlexSelGRID,
    SchemeFlexSelGRD1,
    SchemeFlexSelGRD2,
    SchemeFlexSelGRDM,
    SchemeFlexSelHGRD,
    SchemeFlexSelDWDL,
    SchemeFlexSelSPRL,
    SchemeFlexActFloat,
    SchemeFlexInaFloat,
    SchemeFlexSelFloat,
    #endif // BAR_FLEXWINTITLE_PATCH
}; /* color schemes */

enum {
    NetSupported, NetWMName, NetWMState, NetWMCheck,
    NetWMFullscreen, NetActiveWindow, NetWMWindowType,
    NetWMIcon,
    NetSystemTray, NetSystemTrayOP, NetSystemTrayOrientation,
    NetSystemTrayVisual, NetWMWindowTypeDock, NetSystemTrayOrientationHorz,
    NetDesktopNames, NetDesktopViewport, NetNumberOfDesktops, NetCurrentDesktop,
    NetClientList,
    NetClientListStacking,      /* 支持视屏会议选中单个窗口 */
    NetLast
}; /* EWMH atoms */

enum {
    WMProtocols,
    WMDelete,
    WMState,
    WMTakeFocus,
    #if WINDOWROLERULE_PATCH
    WMWindowRole,
    #endif // WINDOWROLERULE_PATCH
    WMLast
}; /* default atoms */

#if SEAMLESS_RESTART_PATCH
enum {
    ClientFields,
    ClientTags,
    ClientLast
}; /* dwm client atoms */
#endif // SEAMLESS_RESTART_PATCH

enum {
    ClkTagBar,
    ClkLtSymbol,
    ClkStatusText,
    ClkWinTitle,
    ClkClientWin,
    ClkRootWin,
    #if XKB_PATCH
    ClkXKB,
    #endif // XKB_PATCH
    ClkLast
}; /* clicks */

enum {
    BAR_ALIGN_LEFT,
    BAR_ALIGN_CENTER,
    BAR_ALIGN_RIGHT,
    BAR_ALIGN_LEFT_LEFT,
    BAR_ALIGN_LEFT_RIGHT,
    BAR_ALIGN_LEFT_CENTER,
    BAR_ALIGN_NONE,
    BAR_ALIGN_RIGHT_LEFT,
    BAR_ALIGN_RIGHT_RIGHT,
    BAR_ALIGN_RIGHT_CENTER,
    BAR_ALIGN_LAST
}; /* bar alignment */

#if IPC_PATCH
typedef struct TagState TagState;
struct TagState {
       int selected;
       int occupied;
       int urgent;
};

typedef struct ClientState ClientState;
struct ClientState {
       int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen;
};
#endif // IPC_PATCH

typedef union {
    #if IPC_PATCH
    long i;
    unsigned long ui;
    #else
    int i;
    unsigned int ui;
    #endif // IPC_PATCH
    float f;
    const void *v;
} Arg;

typedef struct Monitor Monitor;
typedef struct Bar Bar;
struct Bar {
    Window win;
    Monitor *mon;
    Bar *next;
    int idx;
    int showbar;
    int topbar;
    int external;
    int borderpx;
    int borderscheme;
    int bx, by, bw, bh; /* bar geometry */
    int w[BARRULES]; // width, array length == barrules, then use r index for lookup purposes
    int x[BARRULES]; // x position, array length == ^
};

typedef struct {
    int x;
    int y;
    int h;
    int w;
} BarArg;

typedef struct {
    int monitor;
    int bar;
    int alignment; // see bar alignment enum
    int (*widthfunc)(Bar *bar, BarArg *a);
    int (*drawfunc)(Bar *bar, BarArg *a);
    int (*clickfunc)(Bar *bar, Arg *arg, BarArg *a);
    int (*hoverfunc)(Bar *bar, BarArg *a, XMotionEvent *ev);
    char *name; // for debugging
    int x, w; // position, width for internal use
} BarRule;

typedef struct {
    unsigned int click;
    unsigned int mask;
    unsigned int button;
    void (*func)(const Arg *arg);
    const Arg arg;
} Button;

#if XKB_PATCH
typedef struct XkbInfo XkbInfo;
struct XkbInfo {
    XkbInfo *next;
    XkbInfo *prev;
    int group;
    Window w;
};
#endif // XKB_PATCH

typedef struct Client Client;
struct Client {
    char name[256];
    float mina, maxa;
    #if CFACTS_PATCH
    float cfact;
    #endif // CFACTS_PATCH
    int x, y, w, h;
    int sfx, sfy, sfw, sfh; /* stored float geometry, used on mode revert */
    #if SEAMLESS_RESTART_PATCH
    unsigned int idx;
    #endif // SEAMLESS_RESTART_PATCH
    int oldx, oldy, oldw, oldh;
    int basew, baseh, incw, inch, maxw, maxh, minw, minh, hintsvalid;
    int bw, oldbw;
    unsigned int tags;
    #if SWITCHTAG_PATCH
    unsigned int switchtag;
    #endif // SWITCHTAG_PATCH
    int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen;
    #if !FAKEFULLSCREEN_PATCH && FAKEFULLSCREEN_CLIENT_PATCH
    int fakefullscreen;
    #endif // FAKEFULLSCREEN_CLIENT_PATCH
    unsigned char expandmask;
    int expandx1, expandy1, expandx2, expandy2;
    #if !MAXIMIZE_PATCH
    int wasfloating;
    #endif // MAXIMIZE_PATCH
    #if MAXIMIZE_PATCH
    int ismax, wasfloating;
    #endif // MAXIMIZE_PATCH
    #if AUTORESIZE_PATCH
    int needresize;
    #endif // AUTORESIZE_PATCH
    #if CENTER_PATCH
    int iscentered;
    #endif // CENTER_PATCH
    #if ISPERMANENT_PATCH
    int ispermanent;
    #endif // ISPERMANENT_PATCH
    #if PLACEMOUSE_PATCH
    int beingmoved;
    #endif // PLACEMOUSE_PATCH
    #if SIZEHINTS_ISFREESIZE_PATCH
    int isfreesize;
    #endif // SIZEHINTS_ISFREESIZE_PATCH
    int isterminal, noswallow;
    pid_t pid;
    #if STEAM_PATCH
    int issteam;
    #endif // STEAM_PATCH
    #if STICKY_PATCH
    int issticky;
    #endif // STICKY_PATCH
    Client *next;
    Client *snext;
    Client *swallowing;
    Monitor *mon;
    Window win;
    #if IPC_PATCH
    ClientState prevstate;
    #endif // IPC_PATCH
    #if RENAMED_SCRATCHPADS_PATCH
    char scratchkey;
    #endif // RENAMED_SCRATCHPADS_PATCH
    #if XKB_PATCH
    XkbInfo *xkb;
    #endif // XKB_PATCH
    unsigned int icw, ich;
    Picture icon;
};

typedef struct {
    unsigned int mod;
    KeySym keysym;
    void (*func)(const Arg *);
    const Arg arg;
} Key;

#if FLEXTILE_DELUXE_LAYOUT
typedef struct {
    int nmaster;
    int nstack;
    int layout;
    int masteraxis; // master stack area
    int stack1axis; // primary stack area
    int stack2axis; // secondary stack area, e.g. centered master
    void (*symbolfunc)(Monitor *, unsigned int);
} LayoutPreset;
#endif // FLEXTILE_DELUXE_LAYOUT

typedef struct {
    const char *symbol;
    void (*arrange)(Monitor *);
    #if FLEXTILE_DELUXE_LAYOUT
    LayoutPreset preset;
    #endif // FLEXTILE_DELUXE_LAYOUT
} Layout;

#if PERTAG_PATCH
typedef struct Pertag Pertag;
#endif // PERTAG_PATCH
struct Monitor
{
    char ltsymbol[16];
    float mfact;
    #if FLEXTILE_DELUXE_LAYOUT
    int ltaxis[4];
    int nstack;
    #endif // FLEXTILE_DELUXE_LAYOUT
    int nmaster;
    int num;

    int mx, my, mw, mh;     // 可用区域   /* screen size */
    int wx, wy, ww, wh;     /* window area  */

    #if VANITYGAPS_PATCH
    int gappih;           /* horizontal gap between windows */
    int gappiv;           /* vertical gap between windows */
    int gappoh;           /* horizontal outer gaps */
    int gappov;           /* vertical outer gaps */
    #endif // VANITYGAPS_PATCH
    #if SETBORDERPX_PATCH
    int borderpx;
    #endif // SETBORDERPX_PATCH
    unsigned int seltags;
    unsigned int sellt;
    #if VIEW_HISTORY_PATCH
    unsigned int tagset[NUMVIEWHIST];
    #else
    unsigned int tagset[2];
    #endif // VIEW_HISTORY_PATCH
    int showbar;
    Client *clients;
    Client *sel;
    Client *stack;
    #if FOCUSMASTER_RETURN_PATCH
    Client *tagmarked[32];
    #endif // FOCUSMASTER_RETURN_PATCH
    Monitor *next;
    Bar*    bar;
    const Layout *lt[2];
    #if BAR_ALTERNATIVE_TAGS_PATCH
    unsigned int alttag;
    #endif // BAR_ALTERNATIVE_TAGS_PATCH
    #if PERTAG_PATCH
    Pertag *pertag;
    #endif // PERTAG_PATCH
    #if IPC_PATCH
    char lastltsymbol[16];
    TagState tagstate;
    Client *lastsel;
    const Layout *lastlt;
    #endif // IPC_PATCH
    #if BAR_TAGPREVIEW_PATCH
    Window tagwin;
    int previewshow;
    Pixmap tagmap[NUMTAGS];
    #endif // BAR_TAGPREVIEW_PATCH
};

typedef struct {
    const char *class;
    #if WINDOWROLERULE_PATCH
    const char *role;
    #endif // WINDOWROLERULE_PATCH
    const char *instance;
    const char *title;
    const char *wintype;
    unsigned int tags;
    #if SWITCHTAG_PATCH
    int switchtag;
    #endif // SWITCHTAG_PATCH
    #if CENTER_PATCH
    int iscentered;
    #endif // CENTER_PATCH
    int isfloating;
    #if SELECTIVEFAKEFULLSCREEN_PATCH && FAKEFULLSCREEN_CLIENT_PATCH && !FAKEFULLSCREEN_PATCH
    int isfakefullscreen;
    #endif // SELECTIVEFAKEFULLSCREEN_PATCH
    #if SIZEHINTS_ISFREESIZE_PATCH
    int isfreesize;
    #endif // SIZEHINTS_ISFREESIZE_PATCH
    #if ISPERMANENT_PATCH
    int ispermanent;
    #endif // ISPERMANENT_PATCH
    int isterminal;
    int noswallow;
    #if FLOATPOS_PATCH
    const char *floatpos;
    #endif // FLOATPOS_PATCH
    int monitor;
    #if RENAMED_SCRATCHPADS_PATCH
    const char scratchkey;
    #endif // RENAMED_SCRATCHPADS_PATCH
    int unmanaged;
    #if XKB_PATCH
    int xkb_layout;
    #endif // XKB_PATCH
} Rule;

#if XKB_PATCH
#define RULE(...) { .monitor = -1, .xkb_layout = -1, __VA_ARGS__ },
#else
#define RULE(...) { .monitor = -1, __VA_ARGS__ },
#endif // XKB_PATCH

/* Cross patch compatibility rule macro helper macros */
#define FLOATING , .isfloating = 1
#if CENTER_PATCH
#define CENTERED , .iscentered = 1
#else
#define CENTERED
#endif // CENTER_PATCH
#if ISPERMANENT_PATCH
#define PERMANENT , .ispermanent = 1
#else
#define PERMANENT
#endif // ISPERMANENT_PATCH
#if SELECTIVEFAKEFULLSCREEN_PATCH && FAKEFULLSCREEN_CLIENT_PATCH && !FAKEFULLSCREEN_PATCH
#define FAKEFULLSCREEN , .isfakefullscreen = 1
#else
#define FAKEFULLSCREEN
#endif // SELECTIVEFAKEFULLSCREEN_PATCH
#define NOSWALLOW , .noswallow = 1
#define TERMINAL , .isterminal = 1
#if SWITCHTAG_PATCH
#define SWITCHTAG , .switchtag = 1
#else
#define SWITCHTAG
#endif // SWITCHTAG_PATCH

#if MONITOR_RULES_PATCH
typedef struct {
    int monitor;
    #if PERTAG_PATCH
    int tag;
    #endif // PERTAG_PATCH
    int layout;
    float mfact;
    int nmaster;
    int showbar;
    int topbar;
} MonitorRule;
#endif // MONITOR_RULES_PATCH

/* function declarations */
static void timer_handler(int signum);
static void apply_rules(Client *c);
static int applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact);
static void arrange(Monitor *m);
static void arrangemon(Monitor *m);
static void attach(Client *c);
static void attachstack(Client *c);
static void buttonpress(XEvent *e);
static void checkotherwm(void);
static void cleanup(void);
static void cleanupmon(Monitor *mon);
static void client_message(XEvent *e);
static void configure(Client *c);
static void configure_notify(XEvent *e);
static void configurerequest(XEvent *e);
static Monitor *createmon(void);
static void destroy_notify(XEvent *e);
static void detach(Client *c);
static void detachstack(Client *c);
static Monitor *dirtomon(int dir);
static void drawbar(Monitor *m);
static void drawbars(void);
static void drawbarwin(Bar *bar);
#if !FOCUSONCLICK_PATCH
static void enter_notify(XEvent *e);
#endif // FOCUSONCLICK_PATCH
static void expose(XEvent *e);
static void focus(Client *c);
static void focusin(XEvent *e);
static void focusmon(const Arg *arg);
#if !STACKER_PATCH
static void focusstack(const Arg *arg);
#endif // STACKER_PATCH
static Atom getatomprop(Client *c, Atom prop, Atom req);
static int getrootptr(int *x, int *y);
static long getstate(Window w);
static int gettextprop(Window w, Atom atom, char *text, unsigned int size);
static void grabbuttons(Client *c, int focused);
#if KEYMODES_PATCH
static void grabdefkeys(void);
#else
static void grabkeys(void);
#endif // KEYMODES_PATCH
static void incnmaster(const Arg *arg);
#if KEYMODES_PATCH
static void keydefpress(XEvent *e);
#else
static void keypress(XEvent *e);
#endif // KEYMODES_PATCH
static void killclient(const Arg *arg);
static void manage(Window w, XWindowAttributes *wa);
static void mappingnotify(XEvent *e);
static void maprequest(XEvent *e);
static void motionnotify(XEvent *e);
static void movemouse(const Arg *arg);
static Client *nexttiled(Client *c);
#if !ZOOMSWAP_PATCH || TAGINTOSTACK_ALLMASTER_PATCH || TAGINTOSTACK_ONEMASTER_PATCH
static void pop(Client *c);
#endif // !ZOOMSWAP_PATCH / TAGINTOSTACK_ALLMASTER_PATCH / TAGINTOSTACK_ONEMASTER_PATCH
static void propertynotify(XEvent *e);
static void quit(const Arg *arg);
static Monitor *recttomon(int x, int y, int w, int h);
static void resize(Client *c, int x, int y, int w, int h, int interact);
static void resizeclient(Client *c, int x, int y, int w, int h);
static void resizemouse(const Arg *arg);
static void restack(Monitor *m);
static void run(void);
static void scan(void);
static int sendevent(Window w, Atom proto, int m, long d0, long d1, long d2, long d3, long d4);
static void sendmon(Client *c, Monitor *m);
static void setclientstate(Client *c, long state);
static void setfocus(Client *c);
static void setfullscreen(Client *c, int fullscreen);
static void setlayout(const Arg *arg);
static void setmfact(const Arg *arg);
static void setup(void);
static void seturgent(Client *c, int urg);
static void showhide(Client *c);
static void spawn(const Arg *arg);
#if RIODRAW_PATCH
static pid_t spawncmd(const Arg *arg);
#endif // RIODRAW_PATCH
static void tag(const Arg *arg);
static void tagmon(const Arg *arg);
static void toggle_bar(const Arg *arg);
static void togglefloating(const Arg *arg);
static void toggletag(const Arg *arg);
static void toggleview(const Arg *arg);
static void unfocus(Client *c, int setfocus, Client *nextfocus);
static void unmanage(Client *c, int destroyed);
static void unmap_notify(XEvent *e);
static void updatebarpos(Monitor *m);
static void updatebars(void);
static void updateclientlist(void);
static int updategeom(void);
static void updatenumlockmask(void);
static void updatesizehints(Client *c);
static void updatestatus(void);
static void updatetitle(Client *c);
static void updatewmhints(Client *c);
static void view(const Arg *arg);
static Client *win_to_client(Window w);
static Monitor *win_to_mon(Window w);
static void zoom(const Arg *arg);

static int xerror(Display *dpy, XErrorEvent *ee);
static int xerrordummy(Display *dpy, XErrorEvent *ee);
static int xerror_start(Display *dpy, XErrorEvent *ee);


int update_geom(void);
void update_bar_pos(Monitor *m);

/* bar functions */

#include "patch/include.h"

const char*             gLogPath = "/tmp/graceful-wm.log";

/* variables */
static const char broken[] = "broken";
#if BAR_STATUS2D_PATCH && !BAR_STATUSCOLORS_PATCH
static char stext[1024];
#else
static char stext[512];
#endif // BAR_PANGO_PATCH | BAR_STATUS2D_PATCH
#if BAR_EXTRASTATUS_PATCH || BAR_STATUSCMD_PATCH
#if BAR_STATUS2D_PATCH
static char rawstext[1024];
#else
static char rawstext[512];
#endif // BAR_STATUS2D_PATCH
#endif // BAR_EXTRASTATUS_PATCH | BAR_STATUSCMD_PATCH
#if BAR_EXTRASTATUS_PATCH
#if BAR_STATUS2D_PATCH && !BAR_STATUSCOLORS_PATCH
static char estext[1024];
#else
static char estext[512];
#endif // BAR_STATUS2D_PATCH
#if BAR_STATUSCMD_PATCH
static char rawestext[1024];
#endif // BAR_STATUS2D_PATCH | BAR_STATUSCMD_PATCH
#endif // BAR_EXTRASTATUS_PATCH

#if XKB_PATCH
static int xkbEventType = 0;
#endif // XKB_PATCH
static int screen;
static int sw, sh;           /* X display screen geometry width, height */
static int bh;               /* bar geometry */
static int unmanaged = 0;    /* whether the window manager should manage the new window or not */
static int lrpad;            /* sum of left and right padding for text */
/* Some clients (e.g. alacritty) helpfully send configure requests with a new size or position
 * when they detect that they have been moved to another monitor. This can cause visual glitches
 * when moving (or resizing) client windows from one monitor to another. This variable is used
 * internally to ignore such configure requests while movemouse or resizemouse are being used. */
static int ignoreconfigurerequests = 0;
#if WARP_PATCH
static int force_warp = 0; // force warp in some situations, e.g. killclient
static int ignore_warp = 0; // force skip warp in some situations, e.g. dragmfact, dragcfact
#endif // WARP_PATCH
static int (*xerrorxlib)(Display *, XErrorEvent *);
static unsigned int numlockmask = 0;
#if RIODRAW_PATCH
static int riodimensions[4] = { -1, -1, -1, -1 };
static pid_t riopid = 0;
#endif // RIODRAW_PATCH
static void (*handler[LASTEvent]) (XEvent *) = {
    [ButtonPress] = buttonpress,
    #if COMBO_PATCH || BAR_HOLDBAR_PATCH
    [ButtonRelease] = keyrelease,
    #endif // COMBO_PATCH / BAR_HOLDBAR_PATCH
    [ClientMessage] = client_message,
    [ConfigureRequest] = configurerequest,
    [ConfigureNotify] = configure_notify,
    [DestroyNotify] = destroy_notify,
    #if !FOCUSONCLICK_PATCH
    [EnterNotify] = enter_notify,
    #endif // FOCUSONCLICK_PATCH
    [Expose] = expose,
    [FocusIn] = focusin,
    [KeyPress] = keypress,
    #if COMBO_PATCH || BAR_HOLDBAR_PATCH
    [KeyRelease] = keyrelease,
    #endif // COMBO_PATCH / BAR_HOLDBAR_PATCH
    [MappingNotify] = mappingnotify,
    [MapRequest] = maprequest,
    [MotionNotify] = motionnotify,
    [PropertyNotify] = propertynotify,
    [ResizeRequest] = resizerequest,
    [UnmapNotify] = unmap_notify
};
static Atom wmatom[WMLast], netatom[NetLast];

enum
{
    CUSTOM_STATUS_TIME = 0,
    CUSTOM_STATUS_LAST,
};
static Atom gsCustomStatusAtoms[CUSTOM_STATUS_LAST];

static Atom xatom[XLast];
#if SEAMLESS_RESTART_PATCH
static Atom clientatom[ClientLast];
#endif // SEAMLESS_RESTART_PATCH
#if ON_EMPTY_KEYS_PATCH
static int isempty = 0;
#endif // ON_EMPTY_KEYS_PATCH
static int running = 1;
static Cur *cursor[CurLast];
static Clr **scheme;
static Display *dpy;
static Drw *drw;
static Monitor *mons, *selmon;                      // selmon：当前焦点屏幕，mons：表示所有屏幕列表
static Window root, wmcheckwin;

/* configuration, allows nested code to access above variables */
#include "config.h"

#include "patch/include.c"

/* compile-time check if all tags fit into an unsigned int bit array. */
#if SCRATCHPAD_ALT_1_PATCH
struct NumTags { char limitexceeded[NUMTAGS > 30 ? -1 : 1]; };
#else
struct NumTags { char limitexceeded[NUMTAGS > 31 ? -1 : 1]; };
#endif // SCRATCHPAD_ALT_1_PATCH

/* function implementations */
static void timer_handler(int signum)
{
    Atom utf8string = XInternAtom(dpy, "UTF8_STRING", False);

    time_t currentTime;
    time (&currentTime);

    struct tm* localTime = localtime (&currentTime);
    if (localTime) {
        char buf[32] = {0};
        snprintf (buf, sizeof(buf) - 1, "%4d-%02d-%02d %02d:%02d:%02d",
                  localTime->tm_year + 1900,
                  localTime->tm_mon + 1,
                  localTime->tm_mday,
                  localTime->tm_hour,
                  localTime->tm_min,
                  localTime->tm_sec
                  );
        XChangeProperty(dpy, root, gsCustomStatusAtoms[CUSTOM_STATUS_TIME], utf8string, 8, PropModeReplace, (unsigned char *) (buf), (int) strlen(buf));

        {
            XEvent ev;
            ev.type = ClientMessage;
            ev.xclient.window = root;
            ev.xclient.message_type = gsCustomStatusAtoms[CUSTOM_STATUS_TIME];
            ev.xclient.format = 8;
            ev.xclient.data.l[0] = 0;
            ev.xclient.data.l[1] = 0;
            ev.xclient.data.l[2] = 0;
            ev.xclient.data.l[3] = 0;
            ev.xclient.data.l[4] = 0;
            XSendEvent (dpy, root, False, StructureNotifyMask, &ev);
            XFlush (dpy);
        }
    }
}

void apply_rules(Client *c)
{
    const char *class, *instance;
    Atom wintype;
    #if WINDOWROLERULE_PATCH
    char role[64];
    #endif // WINDOWROLERULE_PATCH
    unsigned int i;
    #if SWITCHTAG_PATCH
    unsigned int newtagset;
    #endif // SWITCHTAG_PATCH
    const Rule *r;
    Monitor *m;
    XClassHint ch = { NULL, NULL };

    /* rule matching */
    c->noswallow = -1;
    #if SIZEHINTS_ISFREESIZE_PATCH
    c->isfreesize = 1;
    #endif // SIZEHINTS_ISFREESIZE_PATCH
    c->isfloating = 0;
    c->tags = 0;
    #if RENAMED_SCRATCHPADS_PATCH
    c->scratchkey = 0;
    #endif // RENAMED_SCRATCHPADS_PATCH
    XGetClassHint(dpy, c->win, &ch);
    class    = ch.res_class ? ch.res_class : broken;
    instance = ch.res_name  ? ch.res_name  : broken;
    wintype  = getatomprop(c, netatom[NetWMWindowType], XA_ATOM);
    #if WINDOWROLERULE_PATCH
    gettextprop(c->win, wmatom[WMWindowRole], role, sizeof(role));
    #endif // WINDOWROLERULE_PATCH

    #if STEAM_PATCH
    if (strstr(class, "Steam") || strstr(class, "steam_app_"))
        c->issteam = 1;
    #endif // STEAM_PATCH

    for (i = 0; i < LENGTH(rules); i++) {
        r = &rules[i];
        if ((!r->title || strstr(c->name, r->title))
        && (!r->class || strstr(class, r->class))
        #if WINDOWROLERULE_PATCH
        && (!r->role || strstr(role, r->role))
        #endif // WINDOWROLERULE_PATCH
        && (!r->instance || strstr(instance, r->instance))
        && (!r->wintype || wintype == XInternAtom(dpy, r->wintype, False)))
        {
            #if CENTER_PATCH
            c->iscentered = r->iscentered;
            #endif // CENTER_PATCH
            #if ISPERMANENT_PATCH
            c->ispermanent = r->ispermanent;
            #endif // ISPERMANENT_PATCH
            #if SELECTIVEFAKEFULLSCREEN_PATCH && FAKEFULLSCREEN_CLIENT_PATCH && !FAKEFULLSCREEN_PATCH
            c->fakefullscreen = r->isfakefullscreen;
            #endif // SELECTIVEFAKEFULLSCREEN_PATCH
            c->isterminal = r->isterminal;
            c->noswallow = r->noswallow;
            #if SIZEHINTS_ISFREESIZE_PATCH
            c->isfreesize = r->isfreesize;
            #endif // SIZEHINTS_ISFREESIZE_PATCH
            c->isfloating = r->isfloating;
            c->tags |= r->tags;
            #if RENAMED_SCRATCHPADS_PATCH
            c->scratchkey = r->scratchkey;
            #elif SCRATCHPADS_PATCH
            if ((r->tags & SPTAGMASK) && r->isfloating) {
                c->x = c->mon->wx + (c->mon->ww / 2 - WIDTH(c) / 2);
                c->y = c->mon->wy + (c->mon->wh / 2 - HEIGHT(c) / 2);
            }
            #endif // SCRATCHPADS_PATCH
            unmanaged = r->unmanaged;
            for (m = mons; m && m->num != r->monitor; m = m->next);
            if (m)
                c->mon = m;
            #if FLOATPOS_PATCH
            if (c->isfloating && r->floatpos) {
                #if CENTER_PATCH
                c->iscentered = 0;
                #endif // CENTER_PATCH
                setfloatpos(c, r->floatpos);
            }
            #endif // FLOATPOS_PATCH

            #if SWITCHTAG_PATCH
            if (r->switchtag && (
                c->noswallow > 0 ||
                !termforwin(c) ||
                !(c->isfloating && swallowfloating && c->noswallow < 0)))
            {
                selmon = c->mon;
                if (r->switchtag == 2 || r->switchtag == 4)
                    newtagset = c->mon->tagset[c->mon->seltags] ^ c->tags;
                else
                    newtagset = c->tags;

                /* Switch to the client's tag, but only if that tag is not already shown */
                if (newtagset && !(c->tags & c->mon->tagset[c->mon->seltags])) {
                    if (r->switchtag == 3 || r->switchtag == 4)
                        c->switchtag = c->mon->tagset[c->mon->seltags];
                    if (r->switchtag == 1 || r->switchtag == 3) {
                        view(&((Arg) { .ui = newtagset }));
                    } else {
                        #if TAGSYNC_PATCH
                        for (m = mons; m; m = m->next)
                            m->tagset[m->seltags] = newtagset;
                        arrange(NULL);
                        #else
                        c->mon->tagset[c->mon->seltags] = newtagset;
                        arrange(c->mon);
                        #endif // TAGSYNC_PATCH
                    }
                }
            }
            #endif // SWITCHTAG_PATCH
            #if XKB_PATCH
            if (r->xkb_layout > -1)
                c->xkb->group = r->xkb_layout;
            #endif // XKB_PATCH
            #if ONLY_ONE_RULE_MATCH_PATCH
            break;
            #endif // ONLY_ONE_RULE_MATCH_PATCH
        }
    }
    if (ch.res_class)
        XFree(ch.res_class);
    if (ch.res_name)
        XFree(ch.res_name);
    #if EMPTYVIEW_PATCH
    if (c->tags & TAGMASK)                    c->tags = c->tags & TAGMASK;
    #if SCRATCHPADS_PATCH && !RENAMED_SCRATCHPADS_PATCH
    else if (c->mon->tagset[c->mon->seltags]) c->tags = c->mon->tagset[c->mon->seltags] & ~SPTAGMASK;
    #elif SCRATCHPAD_ALT_1_PATCH
    else if (c->tags != SCRATCHPAD_MASK && c->mon->tagset[c->mon->seltags]) c->tags = c->mon->tagset[c->mon->seltags];
    #else
    else if (c->mon->tagset[c->mon->seltags]) c->tags = c->mon->tagset[c->mon->seltags];
    #endif // SCRATCHPADS_PATCH
    else                                      c->tags = 1;
    #elif SCRATCHPADS_PATCH && !RENAMED_SCRATCHPADS_PATCH
    c->tags = c->tags & TAGMASK ? c->tags & TAGMASK : (c->mon->tagset[c->mon->seltags] & ~SPTAGMASK);
    #elif SCRATCHPAD_ALT_1_PATCH
    if (c->tags != SCRATCHPAD_MASK)
        c->tags = c->tags & TAGMASK ? c->tags & TAGMASK : c->mon->tagset[c->mon->seltags];
    #else
    c->tags = c->tags & TAGMASK ? c->tags & TAGMASK : c->mon->tagset[c->mon->seltags];
    #endif // EMPTYVIEW_PATCH
}

int
applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact)
{
    int baseismin;
    Monitor *m = c->mon;

    /* set minimum possible */
    *w = MAX(1, *w);
    *h = MAX(1, *h);
    if (interact) {
        if (*x > sw)
            *x = sw - WIDTH(c);
        if (*y > sh)
            *y = sh - HEIGHT(c);
        if (*x + *w + 2 * c->bw < 0)
            *x = 0;
        if (*y + *h + 2 * c->bw < 0)
            *y = 0;
    } else {
        if (*x >= m->wx + m->ww)
            *x = m->wx + m->ww - WIDTH(c);
        if (*y >= m->wy + m->wh)
            *y = m->wy + m->wh - HEIGHT(c);
        if (*x + *w + 2 * c->bw <= m->wx)
            *x = m->wx;
        if (*y + *h + 2 * c->bw <= m->wy)
            *y = m->wy;
    }
    if (*h < bh)
        *h = bh;
    if (*w < bh)
        *w = bh;
    if (resizehints || c->isfloating || !c->mon->lt[c->mon->sellt]->arrange) {
        if (!c->hintsvalid)
            updatesizehints(c);
        /* see last two sentences in ICCCM 4.1.2.3 */
        baseismin = c->basew == c->minw && c->baseh == c->minh;
        if (!baseismin) { /* temporarily remove base dimensions */
            *w -= c->basew;
            *h -= c->baseh;
        }
        /* adjust for aspect limits */
        if (c->mina > 0 && c->maxa > 0) {
            if (c->maxa < (float)*w / *h)
                *w = *h * c->maxa + 0.5;
            else if (c->mina < (float)*h / *w)
                *h = *w * c->mina + 0.5;
        }
        if (baseismin) { /* increment calculation requires this */
            *w -= c->basew;
            *h -= c->baseh;
        }
        /* adjust for increment value */
        if (c->incw)
            *w -= *w % c->incw;
        if (c->inch)
            *h -= *h % c->inch;
        /* restore base dimensions */
        *w = MAX(*w + c->basew, c->minw);
        *h = MAX(*h + c->baseh, c->minh);
        if (c->maxw)
            *w = MIN(*w, c->maxw);
        if (c->maxh)
            *h = MIN(*h, c->maxh);
    }
    return *x != c->x || *y != c->y || *w != c->w || *h != c->h;
}

void
arrange(Monitor *m)
{
    if (m)
        showhide(m->stack);
    else for (m = mons; m; m = m->next)
        showhide(m->stack);
    if (m) {
        arrangemon(m);
        restack(m);
    } else for (m = mons; m; m = m->next)
        arrangemon(m);
}

void
arrangemon(Monitor *m)
{
    strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof m->ltsymbol);
    if (m->lt[m->sellt]->arrange)
        m->lt[m->sellt]->arrange(m);
}

void attach(Client *c)
{
    c->next = c->mon->clients;
    c->mon->clients = c;
}

void
attachstack(Client *c)
{
    c->snext = c->mon->stack;
    c->mon->stack = c;
}

void
buttonpress(XEvent *e)
{
    int click, i, r;

    Arg arg = {0};
    Client *c;
    Monitor *m;
    Bar *bar;
    XButtonPressedEvent *ev = &e->xbutton;
    const BarRule *br;
    BarArg carg = { 0, 0, 0, 0 };
    click = ClkRootWin;

    #if BAR_STATUSCMD_PATCH && !BAR_DWMBLOCKS_PATCH
    *lastbutton = '0' + ev->button;
    #endif // BAR_STATUSCMD_PATCH | BAR_DWMBLOCKS_PATCH

    /* focus monitor if necessary */
    if ((m = win_to_mon(ev->window)) && m != selmon
        #if FOCUSONCLICK_PATCH
        && (focusonwheel || (ev->button != Button4 && ev->button != Button5))
        #endif // FOCUSONCLICK_PATCH
    ) {
        unfocus(selmon->sel, 1, NULL);
        selmon = m;
        focus(NULL);
    }

    for (bar = selmon->bar; bar; bar = bar->next) {
        if (ev->window == bar->win) {
            for (r = 0; r < LENGTH(barrules); r++) {
                br = &barrules[r];
                if (br->bar != bar->idx || (br->monitor == 'A' && m != selmon) || br->clickfunc == NULL)
                    continue;
                if (br->monitor != 'A' && br->monitor != -1 && br->monitor != bar->mon->num)
                    continue;
                if (bar->x[r] <= ev->x && ev->x <= bar->x[r] + bar->w[r]) {
                    carg.x = ev->x - bar->x[r];
                    carg.y = ev->y - bar->borderpx;
                    carg.w = bar->w[r];
                    carg.h = bar->bh - 2 * bar->borderpx;
                    click = br->clickfunc(bar, &arg, &carg);
                    if (click < 0)
                        return;
                    break;
                }
            }
            break;
        }
    }



    if (click == ClkRootWin && (c = win_to_client(ev->window))) {
        #if FOCUSONCLICK_PATCH
        if (focusonwheel || (ev->button != Button4 && ev->button != Button5))
            focus(c);
        #else
        focus(c);
        restack(selmon);
        #endif // FOCUSONCLICK_PATCH
        XAllowEvents(dpy, ReplayPointer, CurrentTime);
        click = ClkClientWin;
    }

    for (i = 0; i < LENGTH(buttons); i++) {
        if (click == buttons[i].click && buttons[i].func && buttons[i].button == ev->button
                && CLEANMASK(buttons[i].mask) == CLEANMASK(ev->state)) {
            buttons[i].func(
                (
                    click == ClkTagBar
                    #if BAR_WINTITLEACTIONS_PATCH
                    || click == ClkWinTitle
                    #endif // BAR_WINTITLEACTIONS_PATCH
                ) && buttons[i].arg.i == 0 ? &arg : &buttons[i].arg
            );
        }
    }
}

void check_other_wm(void)
{
    xerrorxlib = XSetErrorHandler(xerror_start);

    // 当其它窗口管理器在使用时候会主动退出
    // FIXME:// 添加 --replace 选项替换掉当前正在运行的窗口管理器
    XSelectInput(dpy, DefaultRootWindow(dpy), SubstructureRedirectMask);
    XSync(dpy, False);
    XSetErrorHandler(xerror);
    XSync(dpy, False);
}

void cleanup(void)
{
    Monitor *m;
    Layout foo = { "", NULL };
    size_t i;

    #if ALT_TAB_PATCH
    alttabend();
    #endif // ALT_TAB_PATCH

    #if SEAMLESS_RESTART_PATCH
    for (m = mons; m; m = m->next)
        persistmonitorstate(m);
    #endif // SEAMLESS_RESTART_PATCH

    selmon->lt[selmon->sellt] = &foo;
    for (m = mons; m; m = m->next)
        while (m->stack)
            unmanage(m->stack, 0);
    XUngrabKey(dpy, AnyKey, AnyModifier, root);
    while (mons) cleanup_mon(mons);
    if (showsystray && systray) {
        while (systray->icons) {
            removesystrayicon(systray->icons);
        }
        if (systray->win) {
            XUnmapWindow(dpy, systray->win);
            XDestroyWindow(dpy, systray->win);
        }
        free(systray);
    }
    for (i = 0; i < CurLast; i++)
        drw_cur_free(drw, cursor[i]);
    #if BAR_STATUS2D_PATCH && !BAR_STATUSCOLORS_PATCH
    for (i = 0; i < LENGTH(colors) + 1; i++)
    #else
    for (i = 0; i < LENGTH(colors); i++)
    #endif // BAR_STATUS2D_PATCH
        free(scheme[i]);
    free(scheme);
    XDestroyWindow(dpy, wmcheckwin);
    drw_free(drw);
    XSync(dpy, False);
    XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
    XDeleteProperty(dpy, root, netatom[NetActiveWindow]);

    #if IPC_PATCH
    ipc_cleanup();

    if (close(epoll_fd) < 0)
        fprintf(stderr, "Failed to close epoll file descriptor\n");
    #endif // IPC_PATCH
}

void cleanup_mon(Monitor *mon)
{
    Monitor *m;
    Bar *bar;

    if (mon == mons)
        mons = mons->next;
    else {
        for (m = mons; m && m->next != mon; m = m->next);
        m->next = mon->next;
    }
    for (bar = mon->bar; bar; bar = mon->bar) {
        if (!bar->external) {
            XUnmapWindow(dpy, bar->win);
            XDestroyWindow(dpy, bar->win);
        }
        mon->bar = bar->next;
        if (systray && bar == systray->bar)
            systray->bar = NULL;
        free(bar);
    }

    #if PERTAG_PATCH
    free(mon->pertag);
    #endif // PERTAG_PATCH
    #if BAR_TAGPREVIEW_PATCH
    for (size_t i = 0; i < NUMTAGS; i++)
        if (mon->tagmap[i])
            XFreePixmap(dpy, mon->tagmap[i]);
    XUnmapWindow(dpy, mon->tagwin);
    XDestroyWindow(dpy, mon->tagwin);
    #endif // BAR_TAGPREVIEW_PATCH
    free(mon);
}

void client_message(XEvent *e)
{
    XWindowAttributes wa;
    XSetWindowAttributes swa;
    XClientMessageEvent *cme = &e->xclient;
    Client *c = win_to_client(cme->window);
    #if FOCUSONNETACTIVE_PATCH
    unsigned int i;
    #endif // FOCUSONNETACTIVE_PATCH

    if (showsystray && systray && cme->window == systray->win && cme->message_type == netatom[NetSystemTrayOP]) {
        /* add systray icons */
        if (cme->data.l[1] == SYSTEM_TRAY_REQUEST_DOCK) {
            if (!(c = (Client *)calloc(1, sizeof(Client))))
                die("fatal: could not malloc() %u bytes\n", sizeof(Client));
            if (!(c->win = cme->data.l[2])) {
                free(c);
                return;
            }

            c->mon = selmon;
            c->next = systray->icons;
            systray->icons = c;
            XGetWindowAttributes(dpy, c->win, &wa);
            c->x = c->oldx = c->y = c->oldy = 0;
            c->w = c->oldw = wa.width;
            c->h = c->oldh = wa.height;
            c->oldbw = wa.border_width;
            c->bw = 0;
            c->isfloating = True;
            /* reuse tags field as mapped status */
            c->tags = 1;
            updatesizehints(c);
            updatesystrayicongeom(c, wa.width, wa.height);
            XAddToSaveSet(dpy, c->win);
            XSelectInput(dpy, c->win, StructureNotifyMask | PropertyChangeMask | ResizeRedirectMask);
            XClassHint ch = {"dwmsystray", "dwmsystray"};
            XSetClassHint(dpy, c->win, &ch);
            XReparentWindow(dpy, c->win, systray->win, 0, 0);
            /* use parents background color */
            swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
            XChangeWindowAttributes(dpy, c->win, CWBackPixel, &swa);
            sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_EMBEDDED_NOTIFY, 0 , systray->win, XEMBED_EMBEDDED_VERSION);
            XSync(dpy, False);
            setclientstate(c, NormalState);
        }
        return;
    }

    if (!c) { return; }

    if (cme->message_type == netatom[NetWMState]) {
        if (cme->data.l[1] == netatom[NetWMFullscreen]
        || cme->data.l[2] == netatom[NetWMFullscreen]) {
            #if FAKEFULLSCREEN_CLIENT_PATCH && !FAKEFULLSCREEN_PATCH
            if (c->fakefullscreen == 2 && c->isfullscreen)
                c->fakefullscreen = 3;
            #endif // FAKEFULLSCREEN_CLIENT_PATCH
            setfullscreen(c, (cme->data.l[0] == 1 /* _NET_WM_STATE_ADD    */
                || (cme->data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */
                #if !FAKEFULLSCREEN_PATCH
                && !c->isfullscreen
                #endif // !FAKEFULLSCREEN_PATCH
            )));
        }
    } else if (cme->message_type == netatom[NetActiveWindow]) {
        #if FOCUSONNETACTIVE_PATCH
        if (c->tags & c->mon->tagset[c->mon->seltags]) {
            focus(c);
        }
        else {
            for (i = 0; i < NUMTAGS && !((1 << i) & c->tags); i++);
            if (i < NUMTAGS) {
                if (c != selmon->sel) {
                    unfocus(selmon->sel, 0, NULL);
                }
                selmon = c->mon;
                if (((1 << i) & TAGMASK) != selmon->tagset[selmon->seltags]) {
                    view(&((Arg) { .ui = 1 << i }));
                }
                focus(c);
                restack(selmon);
            }
        }
        #else
        if (c != selmon->sel && !c->isurgent) {
            seturgent(c, 1);
        }
        #endif // FOCUSONNETACTIVE_PATCH
    }
}

void configure(Client *c)
{
    XConfigureEvent ce;

    ce.type = ConfigureNotify;
    ce.display = dpy;
    ce.event = c->win;
    ce.window = c->win;
    ce.x = c->x;
    ce.y = c->y;
    ce.width = c->w;
    ce.height = c->h;
    ce.border_width = c->bw;
    ce.above = None;
    ce.override_redirect = False;
    XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&ce);
}

void configure_notify(XEvent *e)
{
    Monitor *m;
    Bar *bar;
    #if !FAKEFULLSCREEN_PATCH
    Client *c;
    #endif // !FAKEFULLSCREEN_PATCH
    XConfigureEvent *ev = &e->xconfigure;
    int dirty;
    /* TODO: updategeom handling sucks, needs to be simplified */
    if (ev->window == root) {
        dirty = (sw != ev->width || sh != ev->height);
        sw = ev->width;
        sh = ev->height;
        if (update_geom() || dirty) {
            drw_resize(drw, sw, sh);
            updatebars();
            for (m = mons; m; m = m->next) {
                #if !FAKEFULLSCREEN_PATCH
                for (c = m->clients; c; c = c->next)
                    #if FAKEFULLSCREEN_CLIENT_PATCH
                    if (c->isfullscreen && c->fakefullscreen != 1)
                    #else
                    if (c->isfullscreen)
                    #endif // FAKEFULLSCREEN_CLIENT_PATCH
                        resizeclient(c, m->mx, m->my, m->mw, m->mh);
                #endif // !FAKEFULLSCREEN_PATCH
                for (bar = m->bar; bar; bar = bar->next)
                    XMoveResizeWindow(dpy, bar->win, bar->bx, bar->by, bar->bw, bar->bh);
                #if BAR_TAGPREVIEW_PATCH
                createpreview(m);
                #endif // BAR_TAGPREVIEW_PATCH
            }
            arrange(NULL);
            focus(NULL);
        }
    }
}

void configurerequest(XEvent *e)
{
    Client *c;
    Monitor *m;

    XConfigureRequestEvent *ev = &e->xconfigurerequest;
    XWindowChanges wc;

    if (ignoreconfigurerequests)
        return;

    if ((c = win_to_client(ev->window))) {
        if (ev->value_mask & CWBorderWidth)
            c->bw = ev->border_width;
        else if (c->isfloating || !selmon->lt[selmon->sellt]->arrange) {
            m = c->mon;
            #if STEAM_PATCH
            if (!c->issteam) {
                if (ev->value_mask & CWX) {
                    c->oldx = c->x;
                    c->x = m->mx + ev->x;
                }
                if (ev->value_mask & CWY) {
                    c->oldy = c->y;
                    c->y = m->my + ev->y;
                }
            }
            #else
            if (ev->value_mask & CWX) {
                c->oldx = c->x;
                c->x = m->mx + ev->x;
            }
            if (ev->value_mask & CWY) {
                c->oldy = c->y;
                c->y = m->my + ev->y;
            }
            #endif // STEAM_PATCH
            if (ev->value_mask & CWWidth) {
                c->oldw = c->w;
                c->w = ev->width;
            }
            if (ev->value_mask & CWHeight) {
                c->oldh = c->h;
                c->h = ev->height;
            }
            if ((c->x + c->w) > m->mx + m->mw && c->isfloating)
                c->x = m->mx + (m->mw / 2 - WIDTH(c) / 2);  /* center in x direction */
            if ((c->y + c->h) > m->my + m->mh && c->isfloating)
                c->y = m->my + (m->mh / 2 - HEIGHT(c) / 2); /* center in y direction */
            if ((ev->value_mask & (CWX|CWY)) && !(ev->value_mask & (CWWidth|CWHeight)))
                configure(c);
            if (ISVISIBLE(c))
                XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h);
            #if AUTORESIZE_PATCH
            else
                c->needresize = 1;
            #endif // AUTORESIZE_PATCH
        } else
            configure(c);
    } else {
        wc.x = ev->x;
        wc.y = ev->y;

        wc.width = ev->width;
        wc.height = ev->height;
        wc.border_width = ev->border_width;
        wc.sibling = ev->above;
        wc.stack_mode = ev->detail;
        XConfigureWindow(dpy, ev->window, ev->value_mask, &wc);
    }
    XSync(dpy, False);
}

Monitor* create_mon(void)
{
    Monitor *m, *mon;
    int i, n, mi, max_bars = 2, istopbar = topbar;
    #if MONITOR_RULES_PATCH
    int layout;
    #endif // MONITOR_RULES_PATCH

    const BarRule *br;
    Bar *bar;
    #if MONITOR_RULES_PATCH
    int j;
    const MonitorRule *mr;
    #endif // MONITOR_RULES_PATCH

    m = ecalloc(1, sizeof(Monitor));
    #if !EMPTYVIEW_PATCH
    #if VIEW_HISTORY_PATCH
    for (i = 0; i < LENGTH(m->tagset); i++)
        m->tagset[i] = 1;
    #else
    m->tagset[0] = m->tagset[1] = 1;
    #endif // VIEW_HISTORY_PATCH
    #endif // EMPTYVIEW_PATCH
    m->mfact = mfact;
    m->nmaster = nmaster;
    #if FLEXTILE_DELUXE_LAYOUT
    m->nstack = nstack;
    #endif // FLEXTILE_DELUXE_LAYOUT
    m->showbar = showbar;

    #if SETBORDERPX_PATCH
    m->borderpx = borderpx;
    #endif // SETBORDERPX_PATCH
    #if VANITYGAPS_PATCH
    m->gappih = gappih;
    m->gappiv = gappiv;
    m->gappoh = gappoh;
    m->gappov = gappov;
    #endif // VANITYGAPS_PATCH
    for (mi = 0, mon = mons; mon; mon = mon->next, mi++); // monitor index
    m->num = mi;
    #if MONITOR_RULES_PATCH
    for (j = 0; j < LENGTH(monrules); j++) {
        mr = &monrules[j];
        if ((mr->monitor == -1 || mr->monitor == m->num)
        #if PERTAG_PATCH
                && (mr->tag <= 0 || (m->tagset[0] & (1 << (mr->tag - 1))))
        #endif // PERTAG_PATCH
        ) {
            layout = MAX(mr->layout, 0);
            layout = MIN(layout, LENGTH(layouts) - 1);
            m->lt[0] = &layouts[layout];
            m->lt[1] = &layouts[1 % LENGTH(layouts)];
            strncpy(m->ltsymbol, layouts[layout].symbol, sizeof m->ltsymbol);

            if (mr->mfact > -1) {
                m->mfact = mr->mfact;
            }

            if (mr->nmaster > -1) {
                m->nmaster = mr->nmaster;
            }

            if (mr->showbar > -1) {
                m->showbar = mr->showbar;
            }

            if (mr->topbar > -1) {
                istopbar = mr->topbar;
            }

            break;
        }
    }
    #else
    m->lt[0] = &layouts[0];
    m->lt[1] = &layouts[1 % LENGTH(layouts)];
    strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol);
    #endif // MONITOR_RULES_PATCH

    /* Derive the number of bars for this monitor based on bar rules */
    for (n = -1, i = 0; i < LENGTH(barrules); i++) {
        br = &barrules[i];
        if (br->monitor == 'A' || br->monitor == -1 || br->monitor == m->num) {
            n = MAX(br->bar, n);
        }
    }

    m->bar = NULL;
    for (i = 0; i <= n && i < max_bars; i++) {
        bar = ecalloc(1, sizeof(Bar));
        bar->mon = m;
        bar->idx = i;
        bar->next = m->bar;
        bar->topbar = istopbar;
        m->bar = bar;
        istopbar = !istopbar;
        bar->showbar = 1;
        bar->external = 0;
        #if BAR_BORDER_PATCH
        bar->borderpx = (barborderpx ? barborderpx : borderpx);
        #else
        bar->borderpx = 0;
        #endif // BAR_BORDER_PATCH
        bar->bh = bh + bar->borderpx * 2;
        bar->borderscheme = SchemeFlexSelMONO;
    }

    #if FLEXTILE_DELUXE_LAYOUT
    m->ltaxis[LAYOUT] = m->lt[0]->preset.layout;
    m->ltaxis[MASTER] = m->lt[0]->preset.masteraxis;
    m->ltaxis[STACK]  = m->lt[0]->preset.stack1axis;
    m->ltaxis[STACK2] = m->lt[0]->preset.stack2axis;
    #endif // FLEXTILE_DELUXE_LAYOUT

    #if PERTAG_PATCH
    if (!(m->pertag = (Pertag *)calloc(1, sizeof(Pertag))))
        die("fatal: could not malloc() %u bytes\n", sizeof(Pertag));
    m->pertag->curtag = 1;
    for (i = 0; i <= NUMTAGS; i++) {
        #if FLEXTILE_DELUXE_LAYOUT
        m->pertag->nstacks[i] = m->nstack;
        #endif // FLEXTILE_DELUXE_LAYOUT

        #if !MONITOR_RULES_PATCH
        /* init nmaster */
        m->pertag->nmasters[i] = m->nmaster;

        /* init mfacts */
        m->pertag->mfacts[i] = m->mfact;

        #if PERTAGBAR_PATCH
        /* init showbar */
        m->pertag->showbars[i] = m->showbar;
        #endif // PERTAGBAR_PATCH
        #endif // MONITOR_RULES_PATCH

        #if ZOOMSWAP_PATCH
        m->pertag->prevzooms[i] = NULL;
        #endif // ZOOMSWAP_PATCH

        /* init layouts */
        #if MONITOR_RULES_PATCH
        for (j = 0; j < LENGTH(monrules); j++) {
            mr = &monrules[j];
            if ((mr->monitor == -1 || mr->monitor == m->num) && (mr->tag == -1 || mr->tag == i)) {
                layout = MAX(mr->layout, 0);
                layout = MIN(layout, LENGTH(layouts) - 1);
                m->pertag->ltidxs[i][0] = &layouts[layout];
                m->pertag->ltidxs[i][1] = m->lt[0];
                m->pertag->nmasters[i] = (mr->nmaster > -1 ? mr->nmaster : m->nmaster);
                m->pertag->mfacts[i] = (mr->mfact > -1 ? mr->mfact : m->mfact);
                #if PERTAGBAR_PATCH
                m->pertag->showbars[i] = (mr->showbar > -1 ? mr->showbar : m->showbar);
                #endif // PERTAGBAR_PATCH
                #if FLEXTILE_DELUXE_LAYOUT
                m->pertag->ltaxis[i][LAYOUT] = m->pertag->ltidxs[i][0]->preset.layout;
                m->pertag->ltaxis[i][MASTER] = m->pertag->ltidxs[i][0]->preset.masteraxis;
                m->pertag->ltaxis[i][STACK]  = m->pertag->ltidxs[i][0]->preset.stack1axis;
                m->pertag->ltaxis[i][STACK2] = m->pertag->ltidxs[i][0]->preset.stack2axis;
                #endif // FLEXTILE_DELUXE_LAYOUT
                break;
            }
        }
        #else
        m->pertag->ltidxs[i][0] = m->lt[0];
        m->pertag->ltidxs[i][1] = m->lt[1];
        #if FLEXTILE_DELUXE_LAYOUT
        /* init flextile axes */
        m->pertag->ltaxis[i][LAYOUT] = m->ltaxis[LAYOUT];
        m->pertag->ltaxis[i][MASTER] = m->ltaxis[MASTER];
        m->pertag->ltaxis[i][STACK]  = m->ltaxis[STACK];
        m->pertag->ltaxis[i][STACK2] = m->ltaxis[STACK2];
        #endif // FLEXTILE_DELUXE_LAYOUT
        #endif // MONITOR_RULES_PATCH
        m->pertag->sellts[i] = m->sellt;

        #if PERTAG_VANITYGAPS_PATCH && VANITYGAPS_PATCH
        m->pertag->enablegaps[i] = 1;
        m->pertag->gaps[i] =
            ((gappoh & 0xFF) << 0) | ((gappov & 0xFF) << 8) | ((gappih & 0xFF) << 16) | ((gappiv & 0xFF) << 24);
        #endif // PERTAG_VANITYGAPS_PATCH | VANITYGAPS_PATCH
    }
    #endif // PERTAG_PATCH

    #if SEAMLESS_RESTART_PATCH
    restoremonitorstate(m);
    #endif // SEAMLESS_RESTART_PATCH

    return m;
}

void destroy_notify(XEvent *e)
{
    Client *c;

    XDestroyWindowEvent *ev = &e->xdestroywindow;

    if ((c = win_to_client(ev->window)))
        unmanage(c, 1);
    else if ((c = swallowingclient(ev->window)))
        unmanage(c->swallowing, 1);
    else if (showsystray && (c = wintosystrayicon(ev->window))) {
        removesystrayicon(c);
        drawbarwin(systray->bar);
    }
}

void
detach(Client *c)
{
    Client **tc;
    #if SEAMLESS_RESTART_PATCH
    c->idx = 0;
    #endif // SEAMLESS_RESTART_PATCH
    #if FOCUSMASTER_RETURN_PATCH
    for (int i = 1; i < NUMTAGS; i++)
        if (c == c->mon->tagmarked[i])
            c->mon->tagmarked[i] = NULL;
    #endif // FOCUSMASTER_RETURN_PATCH

    for (tc = &c->mon->clients; *tc && *tc != c; tc = &(*tc)->next);
    *tc = c->next;
    c->next = NULL;
}

void
detachstack(Client *c)
{
    Client **tc, *t;

    for (tc = &c->mon->stack; *tc && *tc != c; tc = &(*tc)->snext);
    *tc = c->snext;

    if (c == c->mon->sel) {
        for (t = c->mon->stack; t && !ISVISIBLE(t); t = t->snext);
        c->mon->sel = t;
    }
    c->snext = NULL;
}

Monitor* dirtomon(int dir)
{
    Monitor *m = NULL;

    if (dir > 0) {
        if (!(m = selmon->next))
            m = mons;
    } else if (selmon == mons)
        for (m = mons; m->next; m = m->next);
    else
        for (m = mons; m->next != selmon; m = m->next);
    return m;
}

void drawbar(Monitor *m)
{
    #if !BAR_FLEXWINTITLE_PATCH
    if (m->showbar)
    #endif // BAR_FLEXWINTITLE_PATCH
    for (Bar* bar = m->bar; bar; bar = bar->next) {
        drawbarwin(bar);
    }
}

void
drawbars(void)
{
    Monitor *m;
    for (m = mons; m; m = m->next) {
        drawbar(m);
    }
}

void drawbarwin(Bar *bar)
{
    if (!bar || !bar->win || bar->external) {
        return;
    }

    int r, w, total_drawn = 0;
    int rx, lx, rw, lw; // bar size, split between left and right if a center module is added
    const BarRule *br;

    if (bar->borderpx) {
        XSetForeground(drw->dpy, drw->gc, scheme[bar->borderscheme][ColBorder].pixel);
        XFillRectangle(drw->dpy, drw->drawable, drw->gc, 0, 0, bar->bw, bar->bh);
    }

    BarArg warg = { 0 };
    BarArg darg  = { 0 };
    warg.h = bar->bh - 2 * bar->borderpx;

    rw = lw = bar->bw - 2 * bar->borderpx;
    rx = lx = bar->borderpx;

    drw_setscheme(drw, scheme[SchemeNorm]);
    drw_rect(drw, lx, bar->borderpx, lw, bar->bh - 2 * bar->borderpx, 1, 1);
    for (r = 0; r < LENGTH(barrules); r++) {
        br = &barrules[r];
        if (br->bar != bar->idx || !br->widthfunc || (br->monitor == 'A' && bar->mon != selmon)) {
            continue;
        }

        if (br->monitor != 'A' && br->monitor != -1 && br->monitor != bar->mon->num) {
            continue;
        }

        drw_setscheme(drw, scheme[SchemeNorm]);
        warg.w = (br->alignment < BAR_ALIGN_RIGHT_LEFT ? lw : rw);

        w = br->widthfunc(bar, &warg);
        w = MIN(warg.w, w);

        if (lw <= 0) { // if left is exhausted then switch to right side, and vice versa
            lw = rw;
            lx = rx;
        }
        else if (rw <= 0) {
            rw = lw;
            rx = lx;
        }

        switch(br->alignment) {
        default:
        case BAR_ALIGN_NONE:
        case BAR_ALIGN_LEFT_LEFT:
        case BAR_ALIGN_LEFT: {
            bar->x[r] = lx;
            if (lx == rx) {
                rx += w;
                rw -= w;
            }
            lx += w;
            lw -= w;
            break;
        }
        case BAR_ALIGN_LEFT_RIGHT:
        case BAR_ALIGN_RIGHT: {
            bar->x[r] = lx + lw - w;
            if (lx == rx)
                rw -= w;
            lw -= w;
            break;
        }
        case BAR_ALIGN_LEFT_CENTER:
        case BAR_ALIGN_CENTER: {
            bar->x[r] = lx + lw / 2 - w / 2;
            if (lx == rx) {
                rw = rx + rw - bar->x[r] - w;
                rx = bar->x[r] + w;
            }
            lw = bar->x[r] - lx;
            break;
        }
        case BAR_ALIGN_RIGHT_LEFT: {
            bar->x[r] = rx;
            if (lx == rx) {
                lx += w;
                lw -= w;
            }
            rx += w;
            rw -= w;
            break;
        }
        case BAR_ALIGN_RIGHT_RIGHT: {
            bar->x[r] = rx + rw - w;
            if (lx == rx)
                lw -= w;
            rw -= w;
            break;
        }
        case BAR_ALIGN_RIGHT_CENTER: {
            bar->x[r] = rx + rw / 2 - w / 2;
            if (lx == rx) {
                lw = lx + lw - bar->x[r] + w;
                lx = bar->x[r] + w;
            }
            rw = bar->x[r] - rx;
            break;
        }
        }
        bar->w[r] = w;
        darg.x = bar->x[r];
        darg.y = bar->borderpx;
        darg.h = bar->bh - 2 * bar->borderpx;
        darg.w = bar->w[r];
        if (br->drawfunc) {
            total_drawn += br->drawfunc(bar, &darg);
        }
    }

    if (total_drawn == 0 && bar->showbar) {
        bar->showbar = 0;
        update_bar_pos(bar->mon);
        XMoveResizeWindow(dpy, bar->win, bar->bx, bar->by, bar->bw, bar->bh);
        arrange(bar->mon);
    }
    else if (total_drawn > 0 && !bar->showbar) {
        bar->showbar = 1;
        update_bar_pos(bar->mon);
        XMoveResizeWindow(dpy, bar->win, bar->bx, bar->by, bar->bw, bar->bh);
        drw_map(drw, bar->win, 0, 0, bar->bw, bar->bh);
        arrange(bar->mon);
    }
    else {
        drw_map(drw, bar->win, 0, 0, bar->bw, bar->bh);
    }
}

#if !FOCUSONCLICK_PATCH
void enter_notify(XEvent *e)
{
    Client *c;
    #if LOSEFULLSCREEN_PATCH
    Client *sel;
    #endif // LOSEFULLSCREEN_PATCH
    Monitor *m;
    XCrossingEvent *ev = &e->xcrossing;

    if ((ev->mode != NotifyNormal || ev->detail == NotifyInferior) && ev->window != root)
        return;
    c = win_to_client(ev->window);
    m = c ? c->mon : win_to_mon(ev->window);
    if (m != selmon) {
        #if LOSEFULLSCREEN_PATCH
        sel = selmon->sel;
        selmon = m;
        unfocus(sel, 1, c);
        #else
        unfocus(selmon->sel, 1, c);
        selmon = m;
        #endif // LOSEFULLSCREEN_PATCH
    } else if (!c || c == selmon->sel)
        return;
    focus(c);
}
#endif // FOCUSONCLICK_PATCH

void expose(XEvent *e)
{
    Monitor *m;
    XExposeEvent *ev = &e->xexpose;

    if (ev->count == 0 && (m = win_to_mon(ev->window))) {
        drawbar(m);
    }
}

void focus(Client *c)
{
//    if (!c || !ISVISIBLE(c))
//        c = getpointerclient();
    #if STICKY_PATCH
    if (!c || !ISVISIBLE(c))
        for (c = selmon->stack; c && (!ISVISIBLE(c) || c->issticky); c = c->snext);
    #endif // STICKY_PATCH
    if (!c || !ISVISIBLE(c))
        for (c = selmon->stack; c && !ISVISIBLE(c); c = c->snext);
    if (selmon->sel && selmon->sel != c)
        unfocus(selmon->sel, 0, c);
    if (c) {
        if (c->mon != selmon)
            selmon = c->mon;
        if (c->isurgent)
            seturgent(c, 0);
        detachstack(c);
        attachstack(c);
        grabbuttons(c, 1);
        #if !BAR_FLEXWINTITLE_PATCH
        #if RENAMED_SCRATCHPADS_PATCH
        if (c->scratchkey != 0 && c->isfloating)
            XSetWindowBorder(dpy, c->win, scheme[SchemeScratchSel][ColFloat].pixel);
        else if (c->scratchkey != 0)
            XSetWindowBorder(dpy, c->win, scheme[SchemeScratchSel][ColBorder].pixel);
        else if (c->isfloating)
            XSetWindowBorder(dpy, c->win, scheme[SchemeSel][ColFloat].pixel);
        else
            XSetWindowBorder(dpy, c->win, scheme[SchemeSel][ColBorder].pixel);
        #else
        if (c->isfloating)
            XSetWindowBorder(dpy, c->win, scheme[SchemeSel][ColFloat].pixel);
        else
            XSetWindowBorder(dpy, c->win, scheme[SchemeSel][ColBorder].pixel);
        #endif // RENAMED_SCRATCHPADS_PATCH
        #endif // BAR_FLEXWINTITLE_PATCH
        setfocus(c);
    } else {
        XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime);
        XDeleteProperty(dpy, root, netatom[NetActiveWindow]);
    }
    selmon->sel = c;
    drawbars();

    #if ON_EMPTY_KEYS_PATCH
    if ((isempty && selmon->sel) || (!isempty && !selmon->sel)) {
        isempty = !isempty;
        grabkeys();
    }
    #endif // ON_EMPTY_KEYS_PATCH
}

/* there are some broken focus acquiring clients needing extra handling */
void
focusin(XEvent *e)
{
    XFocusChangeEvent *ev = &e->xfocus;

    if (selmon->sel && ev->window != selmon->sel->win)
        setfocus(selmon->sel);
}

void
focusmon(const Arg *arg)
{
    Monitor *m;
    #if LOSEFULLSCREEN_PATCH
    Client *sel;
    #endif // LOSEFULLSCREEN_PATCH

    if (!mons->next)
        return;
    if ((m = dirtomon(arg->i)) == selmon)
        return;
    #if LOSEFULLSCREEN_PATCH
    sel = selmon->sel;
    selmon = m;
    unfocus(sel, 0, NULL);
    #else
    unfocus(selmon->sel, 0, NULL);
    selmon = m;
    #endif // LOSEFULLSCREEN_PATCH
    focus(NULL);
    #if WARP_PATCH
    warp(selmon->sel);
    #endif // WARP_PATCH
}

#if !STACKER_PATCH
void
focusstack(const Arg *arg)
{
    Client *c = NULL, *i;

    #if LOSEFULLSCREEN_PATCH
    if (!selmon->sel)
        return;
    #elif FAKEFULLSCREEN_CLIENT_PATCH && !FAKEFULLSCREEN_PATCH
    if (!selmon->sel || (selmon->sel->isfullscreen && !selmon->sel->fakefullscreen))
        return;
    #else
    if (!selmon->sel || (selmon->sel->isfullscreen && lockfullscreen))
        return;
    #endif // LOSEFULLSCREEN_PATCH
    #if BAR_WINTITLEACTIONS_PATCH
    if (arg->i > 0) {
        for (c = selmon->sel->next; c && (!ISVISIBLE(c) || (arg->i == 1 && HIDDEN(c))); c = c->next);
        if (!c)
            for (c = selmon->clients; c && (!ISVISIBLE(c) || (arg->i == 1 && HIDDEN(c))); c = c->next);
    } else {
        for (i = selmon->clients; i != selmon->sel; i = i->next)
            if (ISVISIBLE(i) && !(arg->i == -1 && HIDDEN(i)))
                c = i;
        if (!c)
            for (; i; i = i->next)
                if (ISVISIBLE(i) && !(arg->i == -1 && HIDDEN(i)))
                    c = i;
    }
    #else
    if (arg->i > 0) {
        for (c = selmon->sel->next; c && !ISVISIBLE(c); c = c->next);
        if (!c)
            for (c = selmon->clients; c && !ISVISIBLE(c); c = c->next);
    } else {
        for (i = selmon->clients; i != selmon->sel; i = i->next)
            if (ISVISIBLE(i))
                c = i;
        if (!c)
            for (; i; i = i->next)
                if (ISVISIBLE(i))
                    c = i;
    }
    #endif // BAR_WINTITLEACTIONS_PATCH
    if (c) {
        focus(c);
        restack(selmon);
    }
}
#endif // STACKER_PATCH

Atom
getatomprop(Client *c, Atom prop, Atom req)
{
    int di;
    unsigned long dl;
    unsigned char *p = NULL;
    Atom da, atom = None;

    if (prop == xatom[XembedInfo]) {
        req = xatom[XembedInfo];
    }

    /* FIXME getatomprop should return the number of items and a pointer to
     * the stored data instead of this workaround */
    if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, req,
        &da, &di, &dl, &dl, &p) == Success && p) {
        atom = *(Atom *)p;
        if (da == xatom[XembedInfo] && dl == 2) {
            atom = ((Atom *)p)[1];
        }
        XFree(p);
    }
    return atom;
}

int getrootptr(int *x, int *y)
{
    int di;
    unsigned int dui;
    Window dummy;

    return XQueryPointer(dpy, root, &dummy, &dummy, x, y, &di, &di, &dui);
}

long
getstate(Window w)
{
    int format;
    long result = -1;
    unsigned char *p = NULL;
    unsigned long n, extra;
    Atom real;

    if (XGetWindowProperty(dpy, w, wmatom[WMState], 0L, 2L, False, wmatom[WMState],
        &real, &format, &n, &extra, (unsigned char **)&p) != Success)
        return -1;
    if (n != 0)
        result = *p;
    XFree(p);
    return result;
}

int
gettextprop(Window w, Atom atom, char *text, unsigned int size)
{
    char **list = NULL;
    int n;
    XTextProperty name;

    if (!text || size == 0) {
        return 0;
    }

    text[0] = '\0';
    if (!XGetTextProperty(dpy, w, &name, atom) || !name.nitems) {
        return 0;
    }

    if (name.encoding == XA_STRING) {
        strncpy(text, (char *)name.value, size - 1);
    }
    else if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success && n > 0 && *list) {
        strncpy(text, *list, size - 1);
        XFreeStringList(list);
    }
    text[size - 1] = '\0';
    XFree(name.value);

    return 1;
}

void
grabbuttons(Client *c, int focused)
{
    updatenumlockmask();
    {
        unsigned int i, j;
        unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask };
        XUngrabButton(dpy, AnyButton, AnyModifier, c->win);
        if (!focused)
            XGrabButton(dpy, AnyButton, AnyModifier, c->win, False,
                BUTTONMASK, GrabModeSync, GrabModeSync, None, None);
        for (i = 0; i < LENGTH(buttons); i++)
            if (buttons[i].click == ClkClientWin
            #if NO_MOD_BUTTONS_PATCH
                && (nomodbuttons || buttons[i].mask != 0)
            #endif // NO_MOD_BUTTONS_PATCH
            )
                for (j = 0; j < LENGTH(modifiers); j++)
                    XGrabButton(dpy, buttons[i].button,
                        buttons[i].mask | modifiers[j],
                        c->win, False, BUTTONMASK,
                        GrabModeAsync, GrabModeSync, None, None);
    }
}

void
#if KEYMODES_PATCH
grabdefkeys(void)
#else
grabkeys(void)
#endif // KEYMODES_PATCH
{
    updatenumlockmask();
    {
        unsigned int i, j, k;
        unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask };
        int start, end, skip;
        KeySym *syms;

        XUngrabKey(dpy, AnyKey, AnyModifier, root);
        XDisplayKeycodes(dpy, &start, &end);
        syms = XGetKeyboardMapping(dpy, start, end - start + 1, &skip);
        if (!syms)
            return;
        for (k = start; k <= end; k++)
            for (i = 0; i < LENGTH(keys); i++)
                /* skip modifier codes, we do that ourselves */
                if (keys[i].keysym == syms[(k - start) * skip])
                    for (j = 0; j < LENGTH(modifiers); j++)
                        XGrabKey(dpy, k,
                             keys[i].mod | modifiers[j],
                             root, True,
                             GrabModeAsync, GrabModeAsync);
        #if ON_EMPTY_KEYS_PATCH
        if (!selmon->sel)
            for (k = start; k <= end; k++)
                for (i = 0; i < LENGTH(on_empty_keys); i++)
                    /* skip modifier codes, we do that ourselves */
                    if (on_empty_keys[i].keysym == syms[(k - start) * skip])
                        for (j = 0; j < LENGTH(modifiers); j++)
                            XGrabKey(dpy, k,
                                 on_empty_keys[i].mod | modifiers[j],
                                 root, True,
                                 GrabModeAsync, GrabModeAsync);
        #endif // ON_EMPTY_KEYS_PATCH
        XFree(syms);
    }
}

void
incnmaster(const Arg *arg)
{
    #if PERTAG_PATCH
    selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag] = MAX(selmon->nmaster + arg->i, 0);
    #else
    selmon->nmaster = MAX(selmon->nmaster + arg->i, 0);
    #endif // PERTAG_PATCH
    arrange(selmon);
}

static int is_unique_geom(XineramaScreenInfo *unique, size_t n, XineramaScreenInfo *info)
{
    while (n--) {
        if (unique[n].x_org == info->x_org
            && unique[n].y_org == info->y_org
            && unique[n].width == info->width
            && unique[n].height == info->height) {
            return 0;
        }
    }

    return 1;
}

void
#if KEYMODES_PATCH
keydefpress(XEvent *e)
#else
keypress(XEvent *e)
#endif // KEYMODES_PATCH
{
    unsigned int i;
    int keysyms_return;
    KeySym* keysym;
    XKeyEvent *ev;

    ev = &e->xkey;
    keysym = XGetKeyboardMapping(dpy, (KeyCode)ev->keycode, 1, &keysyms_return);
    for (i = 0; i < LENGTH(keys); i++)
        if (*keysym == keys[i].keysym
                && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state)
                && keys[i].func)
            keys[i].func(&(keys[i].arg));
    #if ON_EMPTY_KEYS_PATCH
    if (!selmon->sel)
        for (i = 0; i < LENGTH(on_empty_keys); i++)
            if (*keysym == on_empty_keys[i].keysym
                    && CLEANMASK(on_empty_keys[i].mod) == CLEANMASK(ev->state)
                    && on_empty_keys[i].func)
                on_empty_keys[i].func(&(on_empty_keys[i].arg));
    #endif // ON_EMPTY_KEYS_PATCH
    XFree(keysym);
}

void
killclient(const Arg *arg)
{
    #if ISPERMANENT_PATCH
    if (!selmon->sel || selmon->sel->ispermanent)
    #else
    if (!selmon->sel)
    #endif // ISPERMANENT_PATCH
        return;
    if (!sendevent(selmon->sel->win, wmatom[WMDelete], NoEventMask, wmatom[WMDelete], CurrentTime, 0, 0, 0)) {
        XGrabServer(dpy);
        XSetErrorHandler(xerrordummy);
        XSetCloseDownMode(dpy, DestroyAll);
        XKillClient(dpy, selmon->sel->win);
        XSync(dpy, False);
        XSetErrorHandler(xerror);
        XUngrabServer(dpy);
        #if WARP_PATCH
        force_warp = 1;
        #endif // WARP_PATCH
    }
    #if SWAPFOCUS_PATCH && PERTAG_PATCH
    selmon->pertag->prevclient[selmon->pertag->curtag] = NULL;
    #endif // SWAPFOCUS_PATCH
}

void manage(Window w, XWindowAttributes *wa)
{
    Client *c, *t = NULL;
    Client *term = NULL;
    #if SEAMLESS_RESTART_PATCH
    int settings_restored;
    #endif // SEAMLESS_RESTART_PATCH
    Window trans = None;
    XWindowChanges wc;

    c = ecalloc(1, sizeof(Client));
    c->win = w;
    c->pid = winpid(w);
    /* geometry */
    c->sfx = c->sfy = c->sfw = c->sfh = -9999;
    c->x = c->oldx = wa->x;
    c->y = c->oldy = wa->y;
    c->w = c->oldw = wa->width;
    c->h = c->oldh = wa->height;
    c->oldbw = wa->border_width;
    #if CFACTS_PATCH
    c->cfact = 1.0;
    #endif // CFACTS_PATCH
    #if SEAMLESS_RESTART_PATCH
    settings_restored = restoreclientstate(c);
    #endif // SEAMLESS_RESTART_PATCH
    updateicon(c);
    updatetitle(c);

    #if XKB_PATCH
    /* Setting current xkb state must be before applyrules */
    if (!(c->xkb = findxkb(c->win)))
        c->xkb = createxkb(c->win);
    #endif // XKB_PATCH

    if (XGetTransientForHint(dpy, w, &trans) && (t = win_to_client(trans))) {
        c->mon = t->mon;
        c->tags = t->tags;
        #if SETBORDERPX_PATCH
        c->bw = c->mon->borderpx;
        #else
        c->bw = borderpx;
        #endif // SETBORDERPX_PATCH
        #if CENTER_TRANSIENT_WINDOWS_BY_PARENT_PATCH
        c->x = t->x + WIDTH(t) / 2 - WIDTH(c) / 2;
        c->y = t->y + HEIGHT(t) / 2 - HEIGHT(c) / 2;
        #elif CENTER_PATCH && CENTER_TRANSIENT_WINDOWS_PATCH
        c->iscentered = 1;
        #elif CENTER_TRANSIENT_WINDOWS_PATCH
        c->x = c->mon->wx + (c->mon->ww - WIDTH(c)) / 2;
        c->y = c->mon->wy + (c->mon->wh - HEIGHT(c)) / 2;
        #elif CENTER_PATCH
        if (c->x == c->mon->wx && c->y == c->mon->wy)
            c->iscentered = 1;
        #endif // CENTER_TRANSIENT_WINDOWS_PATCH | CENTER_TRANSIENT_WINDOWS_BY_PARENT_PATCH | CENTER_PATCH
    }
    else {
        #if SEAMLESS_RESTART_PATCH
        if (!settings_restored)
            c->mon = selmon;
        #else
        c->mon = selmon;
        #endif // SEAMLESS_RESTART_PATCH
        #if CENTER_PATCH
        if (c->x == c->mon->wx && c->y == c->mon->wy)
            c->iscentered = 1;
        #endif // CENTER_PATCH
        #if SETBORDERPX_PATCH
        c->bw = c->mon->borderpx;
        #else
        c->bw = borderpx;
        #endif // SETBORDERPX_PATCH
        #if SEAMLESS_RESTART_PATCH
        if (!settings_restored)
            apply_rules(c);
        #else
        apply_rules(c);
        #endif // SEAMLESS_RESTART_PATCH
        term = termforwin(c);
        if (term)
            c->mon = term->mon;
    }

    if (unmanaged) {
        XMapWindow(dpy, c->win);
        if (unmanaged == 1) {
            XRaiseWindow(dpy, c->win);
        }
        else if (unmanaged == 2) {
            XLowerWindow(dpy, c->win);
        }

        updatewmhints(c);
        if (!c->neverfocus) {
            XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime);
        }

        sendevent(c->win, wmatom[WMTakeFocus], NoEventMask, wmatom[WMTakeFocus], CurrentTime, 0, 0, 0);

        free(c);
        unmanaged = 0;
        return;
    }

    if (c->x + WIDTH(c) > c->mon->wx + c->mon->ww) {
        c->x = c->mon->wx + c->mon->ww - WIDTH(c);
    }

    if (c->y + HEIGHT(c) > c->mon->wy + c->mon->wh) {
        c->y = c->mon->wy + c->mon->wh - HEIGHT(c);
    }

    c->x = MAX(c->x, c->mon->wx);
    c->y = MAX(c->y, c->mon->wy);

    wc.border_width = c->bw;
    XConfigureWindow(dpy, w, CWBorderWidth, &wc);
    #if !BAR_FLEXWINTITLE_PATCH
    if (c->isfloating)
        XSetWindowBorder(dpy, w, scheme[SchemeNorm][ColFloat].pixel);
    else
        XSetWindowBorder(dpy, w, scheme[SchemeNorm][ColBorder].pixel);
    #endif // BAR_FLEXWINTITLE_PATCH
    configure(c); /* propagates border_width, if size doesn't change */
    updatesizehints(c);
    updatewmhints(c);
    #if DECORATION_HINTS_PATCH
    updatemotifhints(c);
    #endif // DECORATION_HINTS_PATCH

    #if CENTER_PATCH && SAVEFLOATS_PATCH || CENTER_PATCH && 1
    if (c->iscentered) {
        c->sfx = c->x = c->mon->wx + (c->mon->ww - WIDTH(c)) / 2;
        c->sfy = c->y = c->mon->wy + (c->mon->wh - HEIGHT(c)) / 2;
    }
    #elif CENTER_PATCH
    if (c->iscentered) {
        c->x = c->mon->wx + (c->mon->ww - WIDTH(c)) / 2;
        c->y = c->mon->wy + (c->mon->wh - HEIGHT(c)) / 2;
    }
    #elif ALWAYSCENTER_PATCH && SAVEFLOATS_PATCH || ALWAYSCENTER_PATCH && 1
    c->sfx = c->x = c->mon->wx + (c->mon->ww - WIDTH(c)) / 2;
    c->sfy = c->y = c->mon->wy + (c->mon->wh - HEIGHT(c)) / 2;
    #elif ALWAYSCENTER_PATCH
    c->x = c->mon->wx + (c->mon->ww - WIDTH(c)) / 2;
    c->y = c->mon->wy + (c->mon->wh - HEIGHT(c)) / 2;
    #endif // CENTER_PATCH / ALWAYSCENTER_PATCH / SAVEFLOATS_PATCH
    #if SAVEFLOATS_PATCH || 1
    if (c->sfw == -9999) {
        c->sfw = c->w;
        c->sfh = c->h;
    }
    #endif // SAVEFLOATS_PATCH / EXRESIZE_PATCH

    if (getatomprop(c, netatom[NetWMState], XA_ATOM) == netatom[NetWMFullscreen])
        setfullscreen(c, 1);

    XSelectInput(dpy, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask);
    grabbuttons(c, 0);
    #if MAXIMIZE_PATCH
    c->wasfloating = 0;
    c->ismax = 0;
    #elif 1
    c->wasfloating = 0;
    #endif // MAXIMIZE_PATCH / EXRESIZE_PATCH

    if (!c->isfloating)
        c->isfloating = c->oldstate = trans != None || c->isfixed;
    if (c->isfloating) {
        XRaiseWindow(dpy, c->win);
        XSetWindowBorder(dpy, w, scheme[SchemeNorm][ColFloat].pixel);
    }
    #if 1||ATTACHASIDE_PATCH || ATTACHBELOW_PATCH || ATTACHBOTTOM_PATCH || SEAMLESS_RESTART_PATCH
    attachx(c);
    #else
    attach(c);
    #endif
    attachstack(c);
    XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend,
        (unsigned char *) &(c->win), 1);
    XChangeProperty(dpy, root, netatom[NetClientListStacking], XA_WINDOW, 32, PropModePrepend, (unsigned char *) &(c->win), 1);
    XMoveResizeWindow(dpy, c->win, c->x + 2 * sw, c->y, c->w, c->h); /* some windows require this */

    #if BAR_WINTITLEACTIONS_PATCH
    if (!HIDDEN(c))
        setclientstate(c, NormalState);
    #else
    setclientstate(c, NormalState);
    #endif // BAR_WINTITLEACTIONS_PATCH
    if (c->mon == selmon)
        unfocus(selmon->sel, 0, c);
    c->mon->sel = c;
    if (!(term && swallow(term, c))) {
        #if RIODRAW_PATCH
        if (riopid && (!riodraw_matchpid || isdescprocess(riopid, c->pid))) {
            if (riodimensions[3] != -1)
                rioposition(c, riodimensions[0], riodimensions[1], riodimensions[2], riodimensions[3]);
            else {
                killclient(&((Arg) { .v = c }));
                return;
            }
        }
        #endif // RIODRAW_PATCH
        arrange(c->mon);
        #if BAR_WINTITLEACTIONS_PATCH
        if (!HIDDEN(c))
            XMapWindow(dpy, c->win);
        #else
        XMapWindow(dpy, c->win);
        #endif // BAR_WINTITLEACTIONS_PATCH
    }
    focus(NULL);
    setfloatinghint(c);
}

void mappingnotify(XEvent *e)
{
    XMappingEvent *ev = &e->xmapping;

    XRefreshKeyboardMapping(ev);
    if (ev->request == MappingKeyboard) {
        grabkeys();
    }
}

void maprequest(XEvent *e)
{
    static XWindowAttributes wa;
    XMapRequestEvent *ev = &e->xmaprequest;

    Client *i;
    if (showsystray && systray && (i = wintosystrayicon(ev->window))) {
        sendevent(i->win, netatom[Xembed], StructureNotifyMask, CurrentTime, XEMBED_WINDOW_ACTIVATE, 0, systray->win, XEMBED_EMBEDDED_VERSION);
        drawbarwin(systray->bar);
    }

    if (!XGetWindowAttributes(dpy, ev->window, &wa) || wa.override_redirect)
        return;

    if (!win_to_client(ev->window))
        manage(ev->window, &wa);
}

void
motionnotify(XEvent *e)
{
    #if !FOCUSONCLICK_PATCH
    static Monitor *mon = NULL;
    Monitor *m;
    #if LOSEFULLSCREEN_PATCH
    Client *sel;
    #endif // LOSEFULLSCREEN_PATCH
    #endif // FOCUSONCLICK_PATCH
    Bar *bar;
    XMotionEvent *ev = &e->xmotion;

    if ((bar = wintobar(ev->window))) {
        barhover(e, bar);
        return;
    }

    #if BAR_TAGPREVIEW_PATCH
    if (selmon->previewshow != 0)
        hidetagpreview(selmon);
    #endif // BAR_TAGPREVIEW_PATCH

    #if !FOCUSONCLICK_PATCH
    if (ev->window != root)
        return;
    if ((m = recttomon(ev->x_root, ev->y_root, 1, 1)) != mon && mon) {
        #if LOSEFULLSCREEN_PATCH
        sel = selmon->sel;
        selmon = m;
        unfocus(sel, 1, NULL);
        #else
        unfocus(selmon->sel, 1, NULL);
        selmon = m;
        #endif // LOSEFULLSCREEN_PATCH
        focus(NULL);
    }
    mon = m;
    #endif // FOCUSONCLICK_PATCH
}

void
movemouse(const Arg *arg)
{
    int x, y, ocx, ocy, nx, ny;
    Client *c;
    Monitor *m;
    XEvent ev;
    Time lasttime = 0;

    if (!(c = selmon->sel))
        return;
    #if !FAKEFULLSCREEN_PATCH
    #if FAKEFULLSCREEN_CLIENT_PATCH && !FAKEFULLSCREEN_PATCH
    if (c->isfullscreen && c->fakefullscreen != 1) /* no support moving fullscreen windows by mouse */
        return;
    #else
    if (c->isfullscreen) /* no support moving fullscreen windows by mouse */
        return;
    #endif // FAKEFULLSCREEN_CLIENT_PATCH
    #endif // FAKEFULLSCREEN_PATCH
    restack(selmon);
    nx = ocx = c->x;
    ny = ocy = c->y;
    if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync,
        None, cursor[CurMove]->cursor, CurrentTime) != GrabSuccess)
        return;
    if (!getrootptr(&x, &y))
        return;
    ignoreconfigurerequests = 1;
    do {
        XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev);
        switch(ev.type) {
        case ConfigureRequest:
        case Expose:
        case MapRequest:
            handler[ev.type](&ev);
            break;
        case MotionNotify:
            if ((ev.xmotion.time - lasttime) <= (1000 / 60))
                continue;
            lasttime = ev.xmotion.time;

            nx = ocx + (ev.xmotion.x - x);
            ny = ocy + (ev.xmotion.y - y);
            if (abs(selmon->wx - nx) < snap)
                nx = selmon->wx;
            else if (abs((selmon->wx + selmon->ww) - (nx + WIDTH(c))) < snap)
                nx = selmon->wx + selmon->ww - WIDTH(c);
            if (abs(selmon->wy - ny) < snap)
                ny = selmon->wy;
            else if (abs((selmon->wy + selmon->wh) - (ny + HEIGHT(c))) < snap)
                ny = selmon->wy + selmon->wh - HEIGHT(c);
            if (!c->isfloating && selmon->lt[selmon->sellt]->arrange
            && (abs(nx - c->x) > snap || abs(ny - c->y) > snap)) {
                #if SAVEFLOATS_PATCH || 1
                c->sfx = -9999; // disable savefloats when using movemouse
                #endif // SAVEFLOATS_PATCH | EXRESIZE_PATCH
                togglefloating(NULL);
            }
            if (!selmon->lt[selmon->sellt]->arrange || c->isfloating) {
                resize(c, nx, ny, c->w, c->h, 1);
            }
            break;
        }
    } while (ev.type != ButtonRelease);

    XUngrabPointer(dpy, CurrentTime);
    if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) {
        #if SCRATCHPADS_PATCH && !RENAMED_SCRATCHPADS_PATCH
        if (c->tags & SPTAGMASK) {
            c->mon->tagset[c->mon->seltags] ^= (c->tags & SPTAGMASK);
            m->tagset[m->seltags] |= (c->tags & SPTAGMASK);
        }
        #endif // SCRATCHPADS_PATCH
        sendmon(c, m);
        selmon = m;
        focus(NULL);
    }
    /* save last known float coordinates */
    if (!selmon->lt[selmon->sellt]->arrange || c->isfloating) {
        c->sfx = nx;
        c->sfy = ny;
    }
    ignoreconfigurerequests = 0;
}

Client *
nexttiled(Client *c)
{
    #if BAR_WINTITLEACTIONS_PATCH
    for (; c && (c->isfloating || !ISVISIBLE(c) || HIDDEN(c)); c = c->next);
    #else
    for (; c && (c->isfloating || !ISVISIBLE(c)); c = c->next);
    #endif // BAR_WINTITLEACTIONS_PATCH
    return c;
}

#if !ZOOMSWAP_PATCH || TAGINTOSTACK_ALLMASTER_PATCH || TAGINTOSTACK_ONEMASTER_PATCH
void
pop(Client *c)
{
    #if FOCUSMASTER_RETURN_PATCH
    int i;
    for (i = 0; !(selmon->tagset[selmon->seltags] & 1 << i); i++);
    i++;

    c->mon->tagmarked[i] = nexttiled(c->mon->clients);
    #endif // FOCUSMASTER_RETURN_PATCH
    detach(c);
    attach(c);
    focus(c);
    arrange(c->mon);
}
#endif // !ZOOMSWAP_PATCH / TAGINTOSTACK_ALLMASTER_PATCH / TAGINTOSTACK_ONEMASTER_PATCH

void
propertynotify(XEvent *e)
{
    Client *c;
    Window trans;
    XPropertyEvent *ev = &e->xproperty;

    if (showsystray && (c = wintosystrayicon(ev->window))) {
        if (ev->atom == XA_WM_NORMAL_HINTS) {
            updatesizehints(c);
            updatesystrayicongeom(c, c->w, c->h);
        }
        else {
            updatesystrayiconstate(c, ev);
        }
        drawbarwin(systray->bar);
    }

    if ((ev->window == root) && (ev->atom == gsCustomStatusAtoms[CUSTOM_STATUS_TIME])) {
        #if DWMC_PATCH || FSIGNAL_PATCH
        if (!fake_signal())
            updatestatus();
        #else
        updatestatus();
        #endif // DWMC_PATCH / FSIGNAL_PATCH
    } else if (ev->state == PropertyDelete) {
        return; /* ignore */
    } else if ((c = win_to_client(ev->window))) {
        switch(ev->atom) {
        default: break;
        case XA_WM_TRANSIENT_FOR:
            if (!c->isfloating && (XGetTransientForHint(dpy, c->win, &trans)) &&
                (c->isfloating = (win_to_client(trans)) != NULL))
                arrange(c->mon);
            break;
        case XA_WM_NORMAL_HINTS:
            c->hintsvalid = 0;
            break;
        case XA_WM_HINTS:
            updatewmhints(c);
            if (c->isurgent)
                drawbars();
            break;
        }
        if (ev->atom == XA_WM_NAME || ev->atom == netatom[NetWMName]) {
            updatetitle(c);
            if (c == c->mon->sel)
                drawbar(c->mon);
        }
        #if DECORATION_HINTS_PATCH
        if (ev->atom == motifatom)
            updatemotifhints(c);
        #endif // DECORATION_HINTS_PATCH
        else if (ev->atom == netatom[NetWMIcon]) {
            updateicon(c);
            if (c == c->mon->sel)
                drawbar(c->mon);
        }
    }
}

void
quit(const Arg *arg)
{
    #if ONLYQUITONEMPTY_PATCH
    Monitor *m;
    Client *c;
    unsigned int n = 0;

    for (m = mons; m; m = m->next)
        for (c = m->clients; c; c = c->next, n++);

    if (n <= quit_empty_window_count)
        running = 0;
    else
        fprintf(stderr, "[graceful-wm] not exiting (n=%d)\n", n);

    #else // !ONLYQUITONEMPTY_PATCH
    running = 0;
    #endif // ONLYQUITONEMPTY_PATCH
}

Monitor* recttomon(int x, int y, int w, int h)
{
    Monitor *m, *r = selmon;
    int a, area = 0;

    for (m = mons; m; m = m->next) {
        if ((a = INTERSECT(x, y, w, h, m)) > area) {
            area = a;
            r = m;
        }
    }

    return r;
}

void
resize(Client *c, int x, int y, int w, int h, int interact)
{
    if (applysizehints(c, &x, &y, &w, &h, interact))
        resizeclient(c, x, y, w, h);
}

void
resizeclient(Client *c, int x, int y, int w, int h)
{
    XWindowChanges wc;

    c->oldx = c->x; c->x = wc.x = x;
    c->oldy = c->y; c->y = wc.y = y;
    c->oldw = c->w; c->w = wc.width = w;
    c->oldh = c->h; c->h = wc.height = h;
    c->expandmask = 0;
    wc.border_width = c->bw;
    #if ROUNDED_CORNERS_PATCH
    drawroundedcorners(c);
    #endif // ROUNDED_CORNERS_PATCH
    if (((nexttiled(c->mon->clients) == c && !nexttiled(c->next))
        #if MONOCLE_LAYOUT
        || &monocle == c->mon->lt[c->mon->sellt]->arrange
        #endif // MONOCLE_LAYOUT
        #if DECK_LAYOUT
        || (&deck == c->mon->lt[c->mon->sellt]->arrange &&
            c->mon->nmaster == 0)
        #endif // DECK_LAYOUT
        #if FLEXTILE_DELUXE_LAYOUT
        || (&flextile == c->mon->lt[c->mon->sellt]->arrange && (
            (c->mon->ltaxis[LAYOUT] == NO_SPLIT &&
             c->mon->ltaxis[MASTER] == MONOCLE) ||
            (c->mon->ltaxis[STACK] == MONOCLE &&
             c->mon->nmaster == 0)))
        #endif //FLEXTILE_DELUXE_LAYOUT
        )
        #if FAKEFULLSCREEN_CLIENT_PATCH && !FAKEFULLSCREEN_PATCH
        && (c->fakefullscreen == 1 || !c->isfullscreen)
        #else
        && !c->isfullscreen
        #endif // FAKEFULLSCREEN_CLIENT_PATCH
        && !c->isfloating
        && c->mon->lt[c->mon->sellt]->arrange) {
        c->w = wc.width += c->bw * 2;
        c->h = wc.height += c->bw * 2;
        wc.border_width = 0;
    }
    XConfigureWindow(dpy, c->win, CWX|CWY|CWWidth|CWHeight|CWBorderWidth, &wc);
    configure(c);
    XSync(dpy, False);
}

void
resizemouse(const Arg *arg)
{
    int ocx, ocy, nw, nh, nx, ny;
    #if RESIZEPOINT_PATCH || RESIZECORNERS_PATCH
    int opx, opy, och, ocw;
    int horizcorner, vertcorner;
    unsigned int dui;
    Window dummy;
    #endif // RESIZEPOINT_PATCH | RESIZECORNERS_PATCH
    Client *c;
    Monitor *m;
    XEvent ev;
    Time lasttime = 0;

    if (!(c = selmon->sel))
        return;
    #if !FAKEFULLSCREEN_PATCH
    #if FAKEFULLSCREEN_CLIENT_PATCH
    if (c->isfullscreen && c->fakefullscreen != 1) /* no support resizing fullscreen windows by mouse */
        return;
    #else
    if (c->isfullscreen) /* no support resizing fullscreen windows by mouse */
        return;
    #endif // FAKEFULLSCREEN_CLIENT_PATCH
    #endif // !FAKEFULLSCREEN_PATCH
    restack(selmon);
    nx = ocx = c->x;
    ny = ocy = c->y;
    nh = c->h;
    nw = c->w;
    #if RESIZEPOINT_PATCH
    och = c->h;
    ocw = c->w;
    #elif RESIZECORNERS_PATCH
    och = c->y + c->h;
    ocw = c->x + c->w;
    #endif // RESIZEPOINT_PATCH | RESIZECORNERS_PATCH
    #if RESIZEPOINT_PATCH || RESIZECORNERS_PATCH
    if (!XQueryPointer(dpy, c->win, &dummy, &dummy, &opx, &opy, &nx, &ny, &dui))
        return;
    horizcorner = nx < c->w / 2;
    vertcorner  = ny < c->h / 2;
    if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync,
        None, cursor[horizcorner | (vertcorner << 1)]->cursor, CurrentTime) != GrabSuccess)
        return;
    #if RESIZECORNERS_PATCH
    XWarpPointer (dpy, None, c->win, 0, 0, 0, 0,
            horizcorner ? (-c->bw) : (c->w + c->bw - 1),
            vertcorner ? (-c->bw) : (c->h + c->bw - 1));
    #endif // RESIZECORNERS_PATCH
    #else
    if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync,
        None, cursor[CurResize]->cursor, CurrentTime) != GrabSuccess)
        return;
    XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1);
    #endif // RESIZEPOINT_PATCH | RESIZECORNERS_PATCH
    ignoreconfigurerequests = 1;
    do {
        XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev);
        switch(ev.type) {
        case ConfigureRequest:
        case Expose:
        case MapRequest:
            handler[ev.type](&ev);
            break;
        case MotionNotify:
            if ((ev.xmotion.time - lasttime) <= (1000 / 60))
                continue;
            lasttime = ev.xmotion.time;

            #if RESIZEPOINT_PATCH
            nx = horizcorner ? (ocx + ev.xmotion.x - opx) : c->x;
            ny = vertcorner ? (ocy + ev.xmotion.y - opy) : c->y;
            nw = MAX(horizcorner ? (ocx + ocw - nx) : (ocw + (ev.xmotion.x - opx)), 1);
            nh = MAX(vertcorner ? (ocy + och - ny) : (och + (ev.xmotion.y - opy)), 1);
            #elif RESIZECORNERS_PATCH
            nx = horizcorner ? ev.xmotion.x : c->x;
            ny = vertcorner ? ev.xmotion.y : c->y;
            nw = MAX(horizcorner ? (ocw - nx) : (ev.xmotion.x - ocx - 2 * c->bw + 1), 1);
            nh = MAX(vertcorner ? (och - ny) : (ev.xmotion.y - ocy - 2 * c->bw + 1), 1);
            #else
            nw = MAX(ev.xmotion.x - ocx - 2 * c->bw + 1, 1);
            nh = MAX(ev.xmotion.y - ocy - 2 * c->bw + 1, 1);
            #endif // RESIZEPOINT_PATCH | RESIZECORNERS_PATCH
            if (c->mon->wx + nw >= selmon->wx && c->mon->wx + nw <= selmon->wx + selmon->ww
            && c->mon->wy + nh >= selmon->wy && c->mon->wy + nh <= selmon->wy + selmon->wh)
            {
                if (!c->isfloating && selmon->lt[selmon->sellt]->arrange
                && (abs(nw - c->w) > snap || abs(nh - c->h) > snap)) {
                    c->sfx = -9999; // disable savefloats when using resizemouse
                    togglefloating(NULL);
                }
            }
            if (!selmon->lt[selmon->sellt]->arrange || c->isfloating) {
                resize(c, nx, ny, nw, nh, 1);
            }
            break;
        }
    } while (ev.type != ButtonRelease);

    #if !RESIZEPOINT_PATCH
    #if RESIZECORNERS_PATCH
    XWarpPointer(dpy, None, c->win, 0, 0, 0, 0,
            horizcorner ? (-c->bw) : (c->w + c->bw - 1),
            vertcorner ? (-c->bw) : (c->h + c->bw - 1));
    #else
    XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1);
    #endif // RESIZECORNERS_PATCH
    #endif // RESIZEPOINT_PATCH
    XUngrabPointer(dpy, CurrentTime);
    while (XCheckMaskEvent(dpy, EnterWindowMask, &ev));
    if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) {
        #if SCRATCHPADS_PATCH && !RENAMED_SCRATCHPADS_PATCH
        if (c->tags & SPTAGMASK) {
            c->mon->tagset[c->mon->seltags] ^= (c->tags & SPTAGMASK);
            m->tagset[m->seltags] |= (c->tags & SPTAGMASK);
        }
        #endif // SCRATCHPADS_PATCH
        sendmon(c, m);
        selmon = m;
        focus(NULL);
    }
    /* save last known float dimensions */
    if (!selmon->lt[selmon->sellt]->arrange || c->isfloating) {
        c->sfx = nx;
        c->sfy = ny;
        c->sfw = nw;
        c->sfh = nh;
    }
    ignoreconfigurerequests = 0;
}

void
restack(Monitor *m)
{
    Client *c, *f = NULL;
    XEvent ev;
    XWindowChanges wc;
    #if WARP_PATCH && FLEXTILE_DELUXE_LAYOUT
    int n;
    #endif // WARP_PATCH

    drawbar(m);

    if (!m->sel)
        return;
    if (m->sel->isfloating || !m->lt[m->sellt]->arrange)
        XRaiseWindow(dpy, m->sel->win);
    if (m->lt[m->sellt]->arrange) {
        wc.stack_mode = Below;
        if (m->bar) {
            wc.sibling = m->bar->win;
        } else {
            for (f = m->stack; f && (f->isfloating || !ISVISIBLE(f)); f = f->snext); // find first tiled stack client
            if (f)
                wc.sibling = f->win;
        }
        for (c = m->stack; c; c = c->snext)
            if (!c->isfloating && ISVISIBLE(c) && c != f) {
                XConfigureWindow(dpy, c->win, CWSibling|CWStackMode, &wc);
                wc.sibling = c->win;
            }
    }
    XSync(dpy, False);
    while (XCheckMaskEvent(dpy, EnterWindowMask, &ev));
    #if WARP_PATCH && FLEXTILE_DELUXE_LAYOUT || WARP_PATCH
    #if FLEXTILE_DELUXE_LAYOUT
    for (n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++);
    #endif // FLEXTILE_DELUXE_LAYOUT
    if (m == selmon && (m->tagset[m->seltags] & m->sel->tags) && (
        #if MONOCLE_LAYOUT && FLEXTILE_DELUXE_LAYOUT
        (m->lt[m->sellt]->arrange != &monocle
        && !(m->ltaxis[MASTER] == MONOCLE && (abs(m->ltaxis[LAYOUT] == NO_SPLIT || !m->nmaster || n <= m->nmaster))))
        #elif MONOCLE_LAYOUT
        m->lt[m->sellt]->arrange != &monocle
        #else
        !(m->ltaxis[MASTER] == MONOCLE && (abs(m->ltaxis[LAYOUT] == NO_SPLIT || !m->nmaster || n <= m->nmaster)))
        #endif // FLEXTILE_DELUXE_LAYOUT
        || m->sel->isfloating)
    )
        warp(m->sel);
    #endif // WARP_PATCH
}

#if IPC_PATCH
void
run(void)
{
    int event_count = 0;
    const int MAX_EVENTS = 10;
    struct epoll_event events[MAX_EVENTS];

    XSync(dpy, False);

    /* main event loop */
    while (running) {
        event_count = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);

        for (int i = 0; i < event_count; i++) {
            int event_fd = events[i].data.fd;
            DEBUG("Got event from fd %d\n", event_fd);

            if (event_fd == dpy_fd) {
                // -1 means EPOLLHUP
                if (handlexevent(events + i) == -1)
                    return;
            } else if (event_fd == ipc_get_sock_fd()) {
                ipc_handle_socket_epoll_event(events + i);
            } else if (ipc_is_client_registered(event_fd)) {
                if (ipc_handle_client_epoll_event(events + i, mons, &lastselmon, selmon,
                        NUMTAGS, layouts, LENGTH(layouts)) < 0) {
                    fprintf(stderr, "Error handling IPC event on fd %d\n", event_fd);
                }
            } else {
                fprintf(stderr, "Got event from unknown fd %d, ptr %p, u32 %d, u64 %lu",
                event_fd, events[i].data.ptr, events[i].data.u32,
                events[i].data.u64);
                fprintf(stderr, " with events %d\n", events[i].events);
                return;
            }
        }
    }
}
#else
void run(void)
{
    XEvent ev;
    /* main event loop */
    XSync(dpy, False);
    while (running && !XNextEvent(dpy, &ev)) {

        #if XKB_PATCH
        /* Unfortunately the xkbEventType is not constant hence it can't be part of the
         * normal event handler below */
        if (ev.type == xkbEventType) {
            xkbeventnotify(&ev);
            continue;
        }
        #endif // XKB_PATCH

        if (handler[ev.type]) {
            handler[ev.type](&ev); /* call handler */
        }
    }
}
#endif // IPC_PATCH | RESTARTSIG_PATCH

void scan(void)
{
    scanner = 1;
    char swin[256];
    unsigned int i, num;
    Window d1, d2, *wins = NULL;
    XWindowAttributes wa;

    if (XQueryTree(dpy, root, &d1, &d2, &wins, &num)) {
        for (i = 0; i < num; i++) {
            if (!XGetWindowAttributes(dpy, wins[i], &wa)
            || wa.override_redirect || XGetTransientForHint(dpy, wins[i], &d1))
                continue;

            if (wa.map_state == IsViewable || getstate(wins[i]) == IconicState)
                manage(wins[i], &wa);
            else if (gettextprop(wins[i], netatom[NetClientList], swin, sizeof swin))
                manage(wins[i], &wa);
        }
        for (i = 0; i < num; i++) { /* now the transients */
            if (!XGetWindowAttributes(dpy, wins[i], &wa))
                continue;
            if (XGetTransientForHint(dpy, wins[i], &d1)
            && (wa.map_state == IsViewable || getstate(wins[i]) == IconicState))
                manage(wins[i], &wa);
        }
        XFree(wins);
    }
    scanner = 0;
}

void
sendmon(Client *c, Monitor *m)
{
    Monitor *oldm = selmon;
    if (c->mon == m)
        return;
    #if SENDMON_KEEPFOCUS_PATCH && !1
    int hadfocus = (c == selmon->sel);
    #endif // SENDMON_KEEPFOCUS_PATCH
    unfocus(c, 1, NULL);
    detach(c);
    detachstack(c);
    #if SENDMON_KEEPFOCUS_PATCH && !1
    arrange(c->mon);
    #endif // SENDMON_KEEPFOCUS_PATCH
    c->mon = m;
    #if SCRATCHPADS_PATCH && !RENAMED_SCRATCHPADS_PATCH
    if (!(c->tags & SPTAGMASK))
    #endif // SCRATCHPADS_PATCH
    #if EMPTYVIEW_PATCH
    c->tags = (m->tagset[m->seltags] ? m->tagset[m->seltags] : 1);
    #else
    c->tags = m->tagset[m->seltags]; /* assign tags of target monitor */
    #endif // EMPTYVIEW_PATCH
    #if SENDMON_CENTER_PATCH
    c->x = m->mx + (m->mw - WIDTH(c)) / 2;
    c->y = m->my + (m->mh - HEIGHT(c)) / 2;
    #if SAVEFLOATS_PATCH
    c->sfx = m->mx + (m->mw - c->sfw - 2 * c->bw) / 2;
    c->sfy = m->my + (m->mh - c->sfh - 2 * c->bw) / 2;
    #endif // SAVEFLOATS_PATCH
    #endif // SENDMON_CENTER_PATCH
    #if 1||ATTACHASIDE_PATCH || ATTACHBELOW_PATCH || ATTACHBOTTOM_PATCH
    attachx(c);
    #else
    attach(c);
    #endif
    attachstack(c);
    #if 1
    if (oldm != m)
        arrange(oldm);
    arrange(m);
    focus(c);
    restack(m);
    #elif SENDMON_KEEPFOCUS_PATCH
    arrange(m);
    if (hadfocus) {
        focus(c);
        restack(m);
    } else {
        focus(NULL);
    }
    #else
    arrange(NULL);
    focus(NULL);
    #endif // EXRESIZE_PATCH / SENDMON_KEEPFOCUS_PATCH
    #if SWITCHTAG_PATCH
    if (c->switchtag)
        c->switchtag = 0;
    #endif // SWITCHTAG_PATCH
}

void
setclientstate(Client *c, long state)
{
    long data[] = { state, None };

    XChangeProperty(dpy, c->win, wmatom[WMState], wmatom[WMState], 32,
        PropModeReplace, (unsigned char *)data, 2);
}

int sendevent(Window w, Atom proto, int mask, long d0, long d1, long d2, long d3, long d4)
{
    int n;
    Atom *protocols;
    Atom mt;
    int exists = 0;
    XEvent ev;

    if (proto == wmatom[WMTakeFocus] || proto == wmatom[WMDelete]) {
        mt = wmatom[WMProtocols];
        if (XGetWMProtocols(dpy, w, &protocols, &n)) {
            while (!exists && n--) {
                exists = protocols[n] == proto;
            }
            XFree(protocols);
        }
    }
    else {
        exists = True;
        mt = proto;
    }

    if (exists) {
        ev.type = ClientMessage;
        ev.xclient.window = w;
        ev.xclient.message_type = mt;
        ev.xclient.format = 32;
        ev.xclient.data.l[0] = d0;
        ev.xclient.data.l[1] = d1;
        ev.xclient.data.l[2] = d2;
        ev.xclient.data.l[3] = d3;
        ev.xclient.data.l[4] = d4;
        XSendEvent(dpy, w, False, mask, &ev);
    }
    return exists;
}

void setfocus(Client *c)
{
    if (!c->neverfocus) {
        XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime);
        XChangeProperty(dpy, root, netatom[NetActiveWindow],
            XA_WINDOW, 32, PropModeReplace,
            (unsigned char *) &(c->win), 1);
        #if XKB_PATCH
        XkbLockGroup(dpy, XkbUseCoreKbd, c->xkb->group);
        #endif // XKB_PATCH
    }
    sendevent(c->win, wmatom[WMTakeFocus], NoEventMask, wmatom[WMTakeFocus], CurrentTime, 0, 0, 0);
}

#if FAKEFULLSCREEN_CLIENT_PATCH && !FAKEFULLSCREEN_PATCH
void
setfullscreen(Client *c, int fullscreen)
{
    XEvent ev;
    int savestate = 0, restorestate = 0;

    if ((c->fakefullscreen == 0 && fullscreen && !c->isfullscreen) // normal fullscreen
            || (c->fakefullscreen == 2 && fullscreen)) // fake fullscreen --> actual fullscreen
        savestate = 1; // go actual fullscreen
    else if ((c->fakefullscreen == 0 && !fullscreen && c->isfullscreen) // normal fullscreen exit
            || (c->fakefullscreen >= 2 && !fullscreen)) // fullscreen exit --> fake fullscreen
        restorestate = 1; // go back into tiled

    /* If leaving fullscreen and the window was previously fake fullscreen (2), then restore
     * that while staying in fullscreen. The exception to this is if we are in said state, but
     * the client itself disables fullscreen (3) then we let the client go out of fullscreen
     * while keeping fake fullscreen enabled (as otherwise there will be a mismatch between the
     * client and the window manager's perception of the client's fullscreen state). */
    if (c->fakefullscreen == 2 && !fullscreen && c->isfullscreen) {
        c->fakefullscreen = 1;
        c->isfullscreen = 1;
        fullscreen = 1;
    } else if (c->fakefullscreen == 3) // client exiting actual fullscreen
        c->fakefullscreen = 1;

    if (fullscreen != c->isfullscreen) { // only send property change if necessary
        if (fullscreen)
            XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32,
                PropModeReplace, (unsigned char*)&netatom[NetWMFullscreen], 1);
        else
            XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32,
                PropModeReplace, (unsigned char*)0, 0);
    }

    c->isfullscreen = fullscreen;

    /* Some clients, e.g. firefox, will send a client message informing the window manager
     * that it is going into fullscreen after receiving the above signal. This has the side
     * effect of this function (setfullscreen) sometimes being called twice when toggling
     * fullscreen on and off via the window manager as opposed to the application itself.
     * To protect against obscure issues where the client settings are stored or restored
     * when they are not supposed to we add an additional bit-lock on the old state so that
     * settings can only be stored and restored in that precise order. */
    if (savestate && !(c->oldstate & (1 << 1))) {
        c->oldbw = c->bw;
        c->oldstate = c->isfloating | (1 << 1);
        c->bw = 0;
        c->isfloating = 1;
        resizeclient(c, c->mon->mx, c->mon->my, c->mon->mw, c->mon->mh);
        XRaiseWindow(dpy, c->win);
    } else if (restorestate && (c->oldstate & (1 << 1))) {
        c->bw = c->oldbw;
        c->isfloating = c->oldstate = c->oldstate & 1;
        c->x = c->oldx;
        c->y = c->oldy;
        c->w = c->oldw;
        c->h = c->oldh;
        resizeclient(c, c->x, c->y, c->w, c->h);
        restack(c->mon);
    } else
        resizeclient(c, c->x, c->y, c->w, c->h);

    /* Exception: if the client was in actual fullscreen and we exit out to fake fullscreen
     * mode, then the focus would sometimes drift to whichever window is under the mouse cursor
     * at the time. To avoid this we ask X for all EnterNotify events and just ignore them.
     */
    if (!c->isfullscreen)
        while (XCheckMaskEvent(dpy, EnterWindowMask, &ev));
}
#else
void
setfullscreen(Client *c, int fullscreen)
{
    if (fullscreen && !c->isfullscreen) {
        XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32,
            PropModeReplace, (unsigned char*)&netatom[NetWMFullscreen], 1);
        c->isfullscreen = 1;
        #if !FAKEFULLSCREEN_PATCH
        c->oldbw = c->bw;
        c->oldstate = c->isfloating;
        c->bw = 0;
        c->isfloating = 1;
        resizeclient(c, c->mon->mx, c->mon->my, c->mon->mw, c->mon->mh);
        XRaiseWindow(dpy, c->win);
        #endif // !FAKEFULLSCREEN_PATCH
    } else if (!fullscreen && c->isfullscreen){
        XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32,
            PropModeReplace, (unsigned char*)0, 0);
        c->isfullscreen = 0;
        #if !FAKEFULLSCREEN_PATCH
        c->bw = c->oldbw;
        c->isfloating = c->oldstate;
        c->x = c->oldx;
        c->y = c->oldy;
        c->w = c->oldw;
        c->h = c->oldh;
        resizeclient(c, c->x, c->y, c->w, c->h);
        arrange(c->mon);
        #endif // !FAKEFULLSCREEN_PATCH
    }
    #if FAKEFULLSCREEN_PATCH
    resizeclient(c, c->x, c->y, c->w, c->h);
    #endif // FAKEFULLSCREEN_PATCH
}
#endif // FAKEFULLSCREEN_CLIENT_PATCH

void
setlayout(const Arg *arg)
{
    #if !TOGGLELAYOUT_PATCH
    if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) {
    #endif // TOGGLELAYOUT_PATCH
        #if PERTAG_PATCH
        selmon->pertag->sellts[selmon->pertag->curtag] ^= 1;
        selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag];
        #else
        selmon->sellt ^= 1;
        #endif // PERTAG_PATCH
        if (!selmon->lt[selmon->sellt]->arrange) {
            for (Client *c = selmon->clients ; c ; c = c->next) {
                if (!c->isfloating) {
                    /*restore last known float dimensions*/
                    resize(c, selmon->mx + c->sfx, selmon->my + c->sfy,
                           c->sfw, c->sfh, False);
                }
            }
        }
    #if !TOGGLELAYOUT_PATCH
    }
    #endif // TOGGLELAYOUT_PATCH
    #if TOGGLELAYOUT_PATCH
    if (arg && arg->v && arg->v != selmon->lt[selmon->sellt ^ 1])
    #else
    if (arg && arg->v)
    #endif // TOGGLELAYOUT_PATCH
    #if PERTAG_PATCH
        selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt] = (Layout *)arg->v;
    selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt];
    #else
        selmon->lt[selmon->sellt] = (Layout *)arg->v;
    #endif // PERTAG_PATCH

    #if FLEXTILE_DELUXE_LAYOUT
    if (selmon->lt[selmon->sellt]->preset.nmaster && selmon->lt[selmon->sellt]->preset.nmaster != -1)
        selmon->nmaster = selmon->lt[selmon->sellt]->preset.nmaster;
    if (selmon->lt[selmon->sellt]->preset.nstack && selmon->lt[selmon->sellt]->preset.nstack != -1)
        selmon->nstack = selmon->lt[selmon->sellt]->preset.nstack;

    selmon->ltaxis[LAYOUT] = selmon->lt[selmon->sellt]->preset.layout;
    selmon->ltaxis[MASTER] = selmon->lt[selmon->sellt]->preset.masteraxis;
    selmon->ltaxis[STACK]  = selmon->lt[selmon->sellt]->preset.stack1axis;
    selmon->ltaxis[STACK2] = selmon->lt[selmon->sellt]->preset.stack2axis;

    #if PERTAG_PATCH
    selmon->pertag->ltaxis[selmon->pertag->curtag][LAYOUT] = selmon->ltaxis[LAYOUT];
    selmon->pertag->ltaxis[selmon->pertag->curtag][MASTER] = selmon->ltaxis[MASTER];
    selmon->pertag->ltaxis[selmon->pertag->curtag][STACK]  = selmon->ltaxis[STACK];
    selmon->pertag->ltaxis[selmon->pertag->curtag][STACK2] = selmon->ltaxis[STACK2];
    #endif // PERTAG_PATCH
    #endif // FLEXTILE_DELUXE_LAYOUT
    strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof selmon->ltsymbol);
    if (selmon->sel)
        arrange(selmon);
    else
        drawbar(selmon);
}

/* arg > 1.0 will set mfact absolutely */
void
setmfact(const Arg *arg)
{
    float f;

    if (!arg || !selmon->lt[selmon->sellt]->arrange)
        return;
    f = arg->f < 1.0 ? arg->f + selmon->mfact : arg->f - 1.0;
    if (f < 0.05 || f > 0.95)
        return;
    #if PERTAG_PATCH
    selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag] = f;
    #else
    selmon->mfact = f;
    #endif // PERTAG_PATCH
    arrange(selmon);
}

void setup(void)
{
    int i;
    XSetWindowAttributes wa;
    #if XKB_PATCH
    XkbStateRec xkbstate;
    #endif // XKB_PATCH
    Atom utf8string;

    // timer for date time
    {
        // FIXME:// 重新实现时间显示
        struct itimerval timer;

        struct sigaction timerSA;
        sigemptyset(&timerSA.sa_mask);

        timerSA.sa_flags = 0;
        timerSA.sa_handler = timer_handler;

        sigaction(SIGALRM, &timerSA, NULL);

        // 配置时间间隔
        timer.it_value.tv_sec = 1;
        timer.it_value.tv_usec = 0;
        timer.it_interval.tv_sec = 1;   // 每秒更新一次
        timer.it_interval.tv_usec = 0;
        setitimer (ITIMER_REAL, &timer, NULL);
    }

    {
        struct sigaction sa;
        sigemptyset(&sa.sa_mask);

        sa.sa_flags = SA_NOCLDSTOP | SA_NOCLDWAIT | SA_RESTART;
        sa.sa_handler = SIG_IGN;
        sigaction(SIGCHLD, &sa, NULL);
    }

    while (waitpid(-1, NULL, WNOHANG) > 0);             // 等待所有子进程退出

    putenv("_JAVA_AWT_WM_NONREPARENTING=1");

    /* init screen */
    screen = DefaultScreen(dpy);
    sw = DisplayWidth(dpy, screen);
    sh = DisplayHeight(dpy, screen);
    root = RootWindow(dpy, screen);
    xinit_visual();
    drw = drw_create(dpy, screen, root, sw, sh, visual, depth, cmap);

    if (!drw_font_create(drw, font)) {
        LOG_DIE("no fonts could be loaded.");
    }

    lrpad = drw->fonts->h + horizpadbar;
    bh = drw->fonts->h + vertpadbar;

    update_geom();                                      // 更新 bar 位置、屏幕位置 和 大小

    // 初始化时间
    gsCustomStatusAtoms[CUSTOM_STATUS_TIME] = XInternAtom (dpy, "CUSTOM_STATUS_TIME", False);

    /* init atoms */
    utf8string = XInternAtom(dpy, "UTF8_STRING", False);
    wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False);
    wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
    wmatom[WMState] = XInternAtom(dpy, "WM_STATE", False);
    wmatom[WMTakeFocus] = XInternAtom(dpy, "WM_TAKE_FOCUS", False);
    #if WINDOWROLERULE_PATCH
    wmatom[WMWindowRole] = XInternAtom(dpy, "WM_WINDOW_ROLE", False);
    #endif // WINDOWROLERULE_PATCH
    #if SEAMLESS_RESTART_PATCH
    clientatom[ClientFields] = XInternAtom(dpy, "_DWM_CLIENT_FIELDS", False);
    clientatom[ClientTags] = XInternAtom(dpy, "_DWM_CLIENT_TAGS", False);
    #endif // SEAMLESS_RESTART_PATCH
    netatom[NetActiveWindow] = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False);
    netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False);
    netatom[NetSystemTray] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_S0", False);
    netatom[NetSystemTrayOP] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_OPCODE", False);
    netatom[NetSystemTrayOrientation] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_ORIENTATION", False);
    netatom[NetSystemTrayOrientationHorz] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_ORIENTATION_HORZ", False);
    netatom[NetSystemTrayVisual] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_VISUAL", False);
    netatom[NetWMWindowTypeDock] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", False);
    xatom[Manager] = XInternAtom(dpy, "MANAGER", False);
    xatom[Xembed] = XInternAtom(dpy, "_XEMBED", False);
    xatom[XembedInfo] = XInternAtom(dpy, "_XEMBED_INFO", False);
    netatom[NetDesktopViewport] = XInternAtom(dpy, "_NET_DESKTOP_VIEWPORT", False);
    netatom[NetNumberOfDesktops] = XInternAtom(dpy, "_NET_NUMBER_OF_DESKTOPS", False);
    netatom[NetCurrentDesktop] = XInternAtom(dpy, "_NET_CURRENT_DESKTOP", False);
    netatom[NetDesktopNames] = XInternAtom(dpy, "_NET_DESKTOP_NAMES", False);
    netatom[NetWMIcon] = XInternAtom(dpy, "_NET_WM_ICON", False);
    netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False);
    netatom[NetWMState] = XInternAtom(dpy, "_NET_WM_STATE", False);
    netatom[NetWMCheck] = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False);
    netatom[NetWMFullscreen] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
    netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
    netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False);
    netatom[NetClientListStacking] = XInternAtom(dpy, "_NET_CLIENT_LIST_STACKING", False);
    #if DECORATION_HINTS_PATCH
    motifatom = XInternAtom(dpy, "_MOTIF_WM_HINTS", False);
    #endif // DECORATION_HINTS_PATCH
    /* init cursors */
    cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr);
    cursor[CurResize] = drw_cur_create(drw, XC_sizing);
    #if RESIZEPOINT_PATCH || RESIZECORNERS_PATCH
    cursor[CurResizeBR] = drw_cur_create(drw, XC_bottom_right_corner);
    cursor[CurResizeBL] = drw_cur_create(drw, XC_bottom_left_corner);
    cursor[CurResizeTR] = drw_cur_create(drw, XC_top_right_corner);
    cursor[CurResizeTL] = drw_cur_create(drw, XC_top_left_corner);
    #endif // RESIZEPOINT_PATCH | RESIZECORNERS_PATCH
    cursor[CurResizeHorzArrow] = drw_cur_create(drw, XC_sb_h_double_arrow);
    cursor[CurResizeVertArrow] = drw_cur_create(drw, XC_sb_v_double_arrow);
    #if DRAGCFACT_PATCH
    cursor[CurIronCross] = drw_cur_create(drw, XC_iron_cross);
    #endif // DRAGCFACT_PATCH
    cursor[CurMove] = drw_cur_create(drw, XC_fleur);
    /* init appearance */
    get_vt_colors();
    if (get_luminance(colors[SchemeTagsNorm][ColBg]) > 50) {
        strcpy(colors[SchemeTitleNorm][ColBg], title_bg_light);
        strcpy(colors[SchemeTitleSel][ColBg], title_bg_light);
    } else {
        strcpy(colors[SchemeTitleNorm][ColBg], title_bg_dark);
        strcpy(colors[SchemeTitleSel][ColBg], title_bg_dark);
    }
    #if BAR_STATUS2D_PATCH && !BAR_STATUSCOLORS_PATCH
    scheme = ecalloc(LENGTH(colors) + 1, sizeof(Clr *));
    #if BAR_ALPHA_PATCH
    scheme[LENGTH(colors)] = drw_scm_create(drw, colors[0], alphas[0], ColCount);
    #else
    scheme[LENGTH(colors)] = drw_scm_create(drw, colors[0], ColCount);
    #endif // BAR_ALPHA_PATCH
    #else
    scheme = ecalloc(LENGTH(colors), sizeof(Clr *));
    #endif // BAR_STATUS2D_PATCH
    for (i = 0; i < LENGTH(colors); i++)
        scheme[i] = drw_scm_create(drw, colors[i], alphas[i], ColCount);

    #if BAR_POWERLINE_STATUS_PATCH
    statusscheme = ecalloc(LENGTH(statuscolors), sizeof(Clr *));
    for (i = 0; i < LENGTH(statuscolors); i++)
        statusscheme[i] = drw_scm_create(drw, statuscolors[i], alphas[0], ColCount);

    #endif // BAR_POWERLINE_STATUS_PATCH

    updatebars();
    updatestatus();

    /* supporting window for NetWMCheck */
    wmcheckwin = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0);
    XChangeProperty(dpy, wmcheckwin, netatom[NetWMCheck], XA_WINDOW, 32,
        PropModeReplace, (unsigned char *) &wmcheckwin, 1);
    #if LG3D_PATCH
    XChangeProperty(dpy, wmcheckwin, netatom[NetWMName], utf8string, 8,
        PropModeReplace, (unsigned char *) "LG3D", 4);
    #else
    XChangeProperty(dpy, wmcheckwin, netatom[NetWMName], utf8string, 8,
        PropModeReplace, (unsigned char *) "dwm", 3);
    #endif // LG3D_PATCH
    XChangeProperty(dpy, root, netatom[NetWMCheck], XA_WINDOW, 32,
        PropModeReplace, (unsigned char *) &wmcheckwin, 1);
    /* EWMH support per view */
    XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32,
        PropModeReplace, (unsigned char *) netatom, NetLast);
    setnumdesktops();
    setcurrentdesktop();
    setdesktopnames();
    setviewport();
    XDeleteProperty(dpy, root, netatom[NetClientList]);
    XDeleteProperty(dpy, root, netatom[NetClientListStacking]);
    /* select events */
    wa.cursor = cursor[CurNormal]->cursor;
    wa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask
        |ButtonPressMask|PointerMotionMask|EnterWindowMask
        |LeaveWindowMask|StructureNotifyMask|PropertyChangeMask;
    XChangeWindowAttributes(dpy, root, CWEventMask|CWCursor, &wa);
    XSelectInput(dpy, root, wa.event_mask);

    #if XKB_PATCH
    /* get xkb extension info, events and current state */
    if (!XkbQueryExtension(dpy, NULL, &xkbEventType, NULL, NULL, NULL))
        fputs("warning: can not query xkb extension\n", stderr);
    XkbSelectEventDetails(dpy, XkbUseCoreKbd, XkbStateNotify,
                          XkbAllStateComponentsMask, XkbGroupStateMask);
    XkbGetState(dpy, XkbUseCoreKbd, &xkbstate);
    xkbGlobal.group = xkbstate.locked_group;
    #endif // XKB_PATCH

    grabkeys();
    focus(NULL);
    #if IPC_PATCH
    setupepoll();
    #endif // IPC_PATCH
}


void
seturgent(Client *c, int urg)
{
    XWMHints *wmh;

    c->isurgent = urg;
    if (!(wmh = XGetWMHints(dpy, c->win)))
        return;
    wmh->flags = urg ? (wmh->flags | XUrgencyHint) : (wmh->flags & ~XUrgencyHint);
    XSetWMHints(dpy, c->win, wmh);
    XFree(wmh);
}

void
showhide(Client *c)
{
    if (!c)
        return;
    if (ISVISIBLE(c)) {
        #if !RENAMED_SCRATCHPADS_PATCH
        #if SCRATCHPADS_PATCH && SCRATCHPADS_KEEP_POSITION_AND_SIZE_PATCH
        if (
            (c->tags & SPTAGMASK) &&
            c->isfloating &&
            (
                c->x < c->mon->mx ||
                c->x > c->mon->mx + c->mon->mw ||
                c->y < c->mon->my ||
                c->y > c->mon->my + c->mon->mh
            )
        ) {
            c->x = c->mon->wx + (c->mon->ww / 2 - WIDTH(c) / 2);
            c->y = c->mon->wy + (c->mon->wh / 2 - HEIGHT(c) / 2);
        }
        #elif SCRATCHPADS_PATCH
        if ((c->tags & SPTAGMASK) && c->isfloating) {
            c->x = c->mon->wx + (c->mon->ww / 2 - WIDTH(c) / 2);
            c->y = c->mon->wy + (c->mon->wh / 2 - HEIGHT(c) / 2);
        }
        #endif // SCRATCHPADS_KEEP_POSITION_AND_SIZE_PATCH | SCRATCHPADS_PATCH
        #endif // RENAMED_SCRATCHPADS_PATCH
        /* show clients top down */
        if (!c->mon->lt[c->mon->sellt]->arrange && c->sfx != -9999 && !c->isfullscreen) {
            XMoveResizeWindow(dpy, c->win, c->sfx, c->sfy, c->sfw, c->sfh);
            resize(c, c->sfx, c->sfy, c->sfw, c->sfh, 0);
            showhide(c->snext);
            return;
        }
        #if AUTORESIZE_PATCH
        if (c->needresize) {
            c->needresize = 0;
            XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h);
        } else {
            XMoveWindow(dpy, c->win, c->x, c->y);
        }
        #else
        XMoveWindow(dpy, c->win, c->x, c->y);
        #endif // AUTORESIZE_PATCH
        if ((!c->mon->lt[c->mon->sellt]->arrange || c->isfloating)
            #if !FAKEFULLSCREEN_PATCH
            && !c->isfullscreen
            #endif // !FAKEFULLSCREEN_PATCH
            )
            resize(c, c->x, c->y, c->w, c->h, 0);
        showhide(c->snext);
    } else {
        #if RENAMED_SCRATCHPADS_PATCH && RENAMED_SCRATCHPADS_AUTO_HIDE_PATCH
        /* optional: auto-hide scratchpads when moving to other tags */
        if (c->scratchkey != 0 && !(c->tags & c->mon->tagset[c->mon->seltags]))
            c->tags = 0;
        #endif // RENAMED_SCRATCHPADS_AUTO_HIDE_PATCH
        /* hide clients bottom up */
        showhide(c->snext);
        XMoveWindow(dpy, c->win, WIDTH(c) * -2, c->y);
    }
}

#if RIODRAW_PATCH
void
spawn(const Arg *arg)
{
    spawncmd(arg);
}

pid_t
spawncmd(const Arg *arg)
#else
void
spawn(const Arg *arg)
#endif // RIODRAW_PATCH
{
    struct sigaction sa;

    #if RIODRAW_PATCH
    pid_t pid;
    #endif // RIODRAW_PATCH

    #if RIODRAW_PATCH
    if ((pid = fork()) == 0)
    #else
    if (fork() == 0)
    #endif // RIODRAW_PATCH
    {
        if (dpy)
            close(ConnectionNumber(dpy));

        #if BAR_STATUSCMD_PATCH && !BAR_DWMBLOCKS_PATCH
        if (arg->v == statuscmd) {
            for (int i = 0; i < LENGTH(statuscmds); i++) {
                if (statuscmdn == statuscmds[i].id) {
                    statuscmd[2] = statuscmds[i].cmd;
                    setenv("BUTTON", lastbutton, 1);
                    break;
                }
            }
            if (!statuscmd[2])
                exit(EXIT_SUCCESS);
        }
        #endif // BAR_STATUSCMD_PATCH | BAR_DWMBLOCKS_PATCH
        #if SPAWNCMD_PATCH
        if (selmon->sel) {
            const char* const home = getenv("HOME");
            assert(home && strchr(home, '/'));
            const size_t homelen = strlen(home);
            char *cwd, *pathbuf = NULL;
            struct stat statbuf;

            cwd = strtok(selmon->sel->name, SPAWN_CWD_DELIM);
            /* NOTE: strtok() alters selmon->sel->name in-place,
             * but that does not matter because we are going to
             * exec() below anyway; nothing else will use it */
            while (cwd) {
                if (*cwd == '~') { /* replace ~ with $HOME */
                    if (!(pathbuf = malloc(homelen + strlen(cwd)))) /* ~ counts for NULL term */
                        die("fatal: could not malloc() %u bytes\n", homelen + strlen(cwd));
                    strcpy(strcpy(pathbuf, home) + homelen, cwd + 1);
                    cwd = pathbuf;
                }

                if (strchr(cwd, '/') && !stat(cwd, &statbuf)) {
                    if (!S_ISDIR(statbuf.st_mode))
                        cwd = dirname(cwd);

                    if (!chdir(cwd))
                        break;
                }

                cwd = strtok(NULL, SPAWN_CWD_DELIM);
            }

            free(pathbuf);
        }
        #endif // SPAWNCMD_PATCH
        setsid();

        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;
        sa.sa_handler = SIG_DFL;
        sigaction(SIGCHLD, &sa, NULL);

        execvp(((char **)arg->v)[0], (char **)arg->v);
        die("graceful-wm: execvp '%s' failed:", ((char **)arg->v)[0]);
    }
    #if RIODRAW_PATCH
    return pid;
    #endif // RIODRAW_PATCH
}

void
tag(const Arg *arg)
{
    #if SWAPFOCUS_PATCH && PERTAG_PATCH
    unsigned int tagmask, tagindex;
    #endif // SWAPFOCUS_PATCH

    if (selmon->sel && arg->ui & TAGMASK) {
        selmon->sel->tags = arg->ui & TAGMASK;
        #if SWITCHTAG_PATCH
        if (selmon->sel->switchtag)
            selmon->sel->switchtag = 0;
        #endif // SWITCHTAG_PATCH
        arrange(selmon);
        focus(NULL);
        #if SWAPFOCUS_PATCH && PERTAG_PATCH
        selmon->pertag->prevclient[selmon->pertag->curtag] = NULL;
        for (tagmask = arg->ui & TAGMASK, tagindex = 1; tagmask!=0; tagmask >>= 1, tagindex++)
            if (tagmask & 1)
                selmon->pertag->prevclient[tagindex] = NULL;
        #endif // SWAPFOCUS_PATCH
        #if VIEWONTAG_PATCH
        if ((arg->ui & TAGMASK) != selmon->tagset[selmon->seltags])
            view(arg);
        #endif // VIEWONTAG_PATCH
    }
}

void
tagmon(const Arg *arg)
{
    Client *c = selmon->sel;
    Monitor *dest;
    #if SEAMLESS_RESTART_PATCH && SAVEFLOATS_PATCH
    int restored;
    #endif // SEAMLESS_RESTART_PATCH | SAVEFLOATS_PATCH
    if (!c || !mons->next)
        return;
    dest = dirtomon(arg->i);
    #if SEAMLESS_RESTART_PATCH && SAVEFLOATS_PATCH
    savewindowfloatposition(c, c->mon);
    restored = restorewindowfloatposition(c, dest);
    if (restored && (!dest->lt[dest->sellt]->arrange || c->isfloating)) {
        XMoveResizeWindow(dpy, c->win, c->sfx, c->sfy, c->sfw, c->sfh);
        resize(c, c->sfx, c->sfy, c->sfw, c->sfh, 1);
    }
    #endif // SEAMLESS_RESTART_PATCH | SAVEFLOATS_PATCH
    #if TAGMONFIXFS_PATCH
    if (c->isfullscreen) {
        c->isfullscreen = 0;
        sendmon(c, dest);
        c->isfullscreen = 1;
        #if !FAKEFULLSCREEN_PATCH && FAKEFULLSCREEN_CLIENT_PATCH
        if (c->fakefullscreen != 1) {
            resizeclient(c, c->mon->mx, c->mon->my, c->mon->mw, c->mon->mh);
            XRaiseWindow(dpy, c->win);
        }
        #elif !FAKEFULLSCREEN_PATCH
        resizeclient(c, c->mon->mx, c->mon->my, c->mon->mw, c->mon->mh);
        XRaiseWindow(dpy, c->win);
        #endif // FAKEFULLSCREEN_CLIENT_PATCH
    } else
        sendmon(c, dest);
    #else
    sendmon(c, dest);
    #endif // TAGMONFIXFS_PATCH
}

void toggle_bar(const Arg *arg)
{
    Bar *bar;
    #if BAR_HOLDBAR_PATCH && PERTAG_PATCH && PERTAGBAR_PATCH
    selmon->showbar = selmon->pertag->showbars[selmon->pertag->curtag] = (selmon->showbar == 2 ? 1 : !selmon->showbar);
    #elif BAR_HOLDBAR_PATCH
    selmon->showbar = (selmon->showbar == 2 ? 1 : !selmon->showbar);
    #elif PERTAG_PATCH && PERTAGBAR_PATCH
    selmon->showbar = selmon->pertag->showbars[selmon->pertag->curtag] = !selmon->showbar;
    #else
    selmon->showbar = !selmon->showbar;
    #endif // BAR_HOLDBAR_PATCH | PERTAG_PATCH
    update_bar_pos(selmon);
    for (bar = selmon->bar; bar; bar = bar->next)
        XMoveResizeWindow(dpy, bar->win, bar->bx, bar->by, bar->bw, bar->bh);
    if (!selmon->showbar && systray) {
        XMoveWindow(dpy, systray->win, -32000, -32000);
    }
    arrange(selmon);
}

void
togglefloating(const Arg *arg)
{
    Client *c = selmon->sel;
    if (arg && arg->v)
        c = (Client*)arg->v;
    if (!c)
        return;
    #if !FAKEFULLSCREEN_PATCH
    #if FAKEFULLSCREEN_CLIENT_PATCH
    if (c->isfullscreen && c->fakefullscreen != 1) /* no support for fullscreen windows */
        return;
    #else
    if (c->isfullscreen) /* no support for fullscreen windows */
        return;
    #endif // FAKEFULLSCREEN_CLIENT_PATCH
    #endif // !FAKEFULLSCREEN_PATCH
    c->isfloating = !c->isfloating || c->isfixed;
    #if !BAR_FLEXWINTITLE_PATCH
    if (c->isfloating)
        XSetWindowBorder(dpy, c->win, scheme[SchemeSel][ColFloat].pixel);
    else
        XSetWindowBorder(dpy, c->win, scheme[SchemeSel][ColBorder].pixel);
    #endif // BAR_FLEXWINTITLE_PATCH
    if (c->isfloating) {
        if (c->sfx != -9999) {
            /* restore last known float dimensions */
            resize(c, c->sfx, c->sfy, c->sfw, c->sfh, 0);
        }
        else {
            resize(c, c->x, c->y, c->w, c->h, 0);
        }
    }
    else {
        /* save last known float dimensions */
        c->sfx = c->x;
        c->sfy = c->y;
        c->sfw = c->w;
        c->sfh = c->h;
    }
    arrange(c->mon);
    setfloatinghint(c);
}

void
toggletag(const Arg *arg)
{
    unsigned int newtags;
    #if SWAPFOCUS_PATCH && PERTAG_PATCH
    unsigned int tagmask, tagindex;
    #endif // SWAPFOCUS_PATCH

    if (!selmon->sel)
        return;
    newtags = selmon->sel->tags ^ (arg->ui & TAGMASK);
    if (newtags) {
        selmon->sel->tags = newtags;
        arrange(selmon);
        focus(NULL);
        #if SWAPFOCUS_PATCH && PERTAG_PATCH
        for (tagmask = arg->ui & TAGMASK, tagindex = 1; tagmask!=0; tagmask >>= 1, tagindex++)
            if (tagmask & 1)
                selmon->pertag->prevclient[tagindex] = NULL;
        #endif // SWAPFOCUS_PATCH
    }
    updatecurrentdesktop();
}

void
toggleview(const Arg *arg)
{
    unsigned int newtagset = selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK);;
    #if TAGSYNC_PATCH
    Monitor *origselmon = selmon;
    for (selmon = mons; selmon; selmon = selmon->next) {
    #endif // TAGSYNC_PATCH
    #if PERTAG_PATCH
    int i;
    #endif // PERTAG_PATCH

    #if TAGINTOSTACK_ALLMASTER_PATCH
    Client *const selected = selmon->sel;

    // clients in the master area should be the same after we add a new tag
    Client **const masters = calloc(selmon->nmaster, sizeof(Client *));
    if (!masters) {
        die("fatal: could not calloc() %u bytes \n", selmon->nmaster * sizeof(Client *));
    }
    // collect (from last to first) references to all clients in the master area
    Client *c;
    size_t j;
    for (c = nexttiled(selmon->clients), j = 0; c && j < selmon->nmaster; c = nexttiled(c->next), ++j)
        masters[selmon->nmaster - (j + 1)] = c;
    // put the master clients at the front of the list
    // > go from the 'last' master to the 'first'
    for (j = 0; j < selmon->nmaster; ++j)
        if (masters[j])
            pop(masters[j]);
    free(masters);

    // we also want to be sure not to mutate the focus
    focus(selected);
    #elif TAGINTOSTACK_ONEMASTER_PATCH
    // the first visible client should be the same after we add a new tag
    // we also want to be sure not to mutate the focus
    Client *const c = nexttiled(selmon->clients);
    if (c) {
        Client * const selected = selmon->sel;
        pop(c);
        focus(selected);
    }
    #endif // TAGINTOSTACK_ALLMASTER_PATCH / TAGINTOSTACK_ONEMASTER_PATCH

    #if !EMPTYVIEW_PATCH
    if (newtagset) {
    #endif // EMPTYVIEW_PATCH
        #if BAR_TAGPREVIEW_PATCH
        tagpreviewswitchtag();
        #endif // BAR_TAGPREVIEW_PATCH
        selmon->tagset[selmon->seltags] = newtagset;

        #if PERTAG_PATCH
        #if SCRATCHPADS_PATCH && !RENAMED_SCRATCHPADS_PATCH
        if (newtagset == ~SPTAGMASK)
        #else
        if (newtagset == ~0)
        #endif // SCRATCHPADS_PATCH
        {
            selmon->pertag->curtag = 0;
        }
        /* test if the user did not select the same tag */
        if (!(newtagset & 1 << (selmon->pertag->curtag - 1))) {
            for (i = 0; !(newtagset & 1 << i); i++) ;
            selmon->pertag->curtag = i + 1;
        }

        /* apply settings for this view */
        selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag];
        selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag];
        selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag];
        selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt];
        selmon->lt[selmon->sellt^1] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt^1];
        #if PERTAGBAR_PATCH
        if (selmon->showbar != selmon->pertag->showbars[selmon->pertag->curtag])
            toggle_bar(NULL);
        #endif // PERTAGBAR_PATCH
        #endif // PERTAG_PATCH
        #if !TAGSYNC_PATCH
        arrange(selmon);
        focus(NULL);
        #endif // TAGSYNC_PATCH
    #if !EMPTYVIEW_PATCH
    }
    #endif // EMPTYVIEW_PATCH
    #if TAGSYNC_PATCH
    }
    selmon = origselmon;
    #if !EMPTYVIEW_PATCH
    if (newtagset) {
    #endif // EMPTYVIEW_PATCH
        arrange(NULL);
        focus(NULL);
    #if !EMPTYVIEW_PATCH
    }
    #endif // EMPTYVIEW_PATCH
    #endif // TAGSYNC_PATCH
    updatecurrentdesktop();
}

void
unfocus(Client *c, int setfocus, Client *nextfocus)
{
    if (!c)
        return;
    #if SWAPFOCUS_PATCH && PERTAG_PATCH
    selmon->pertag->prevclient[selmon->pertag->curtag] = c;
    #endif // SWAPFOCUS_PATCH
    #if LOSEFULLSCREEN_PATCH
    if (c->isfullscreen && ISVISIBLE(c) && c->mon == selmon && nextfocus && !nextfocus->isfloating) {
        #if RENAMED_SCRATCHPADS_PATCH && RENAMED_SCRATCHPADS_AUTO_HIDE_PATCH
        #if FAKEFULLSCREEN_CLIENT_PATCH
        if (c->scratchkey != 0 && c->fakefullscreen != 1)
            togglescratch(&((Arg) {.v = (const char*[]){ &c->scratchkey, NULL } }));
        #else
        if (c->scratchkey != 0)
            togglescratch(&((Arg) {.v = (const char*[]){ &c->scratchkey, NULL } }));
        #endif // FAKEFULLSCREEN_CLIENT_PATCH
        else
        #endif // RENAMED_SCRATCHPADS_AUTO_HIDE_PATCH
        #if FAKEFULLSCREEN_CLIENT_PATCH
        if (c->fakefullscreen != 1)
            setfullscreen(c, 0);
        #else
        setfullscreen(c, 0);
        #endif // #if FAKEFULLSCREEN_CLIENT_PATCH
    }
    #endif // LOSEFULLSCREEN_PATCH
    grabbuttons(c, 0);
    #if !BAR_FLEXWINTITLE_PATCH
    #if RENAMED_SCRATCHPADS_PATCH
    if (c->scratchkey != 0 && c->isfloating)
        XSetWindowBorder(dpy, c->win, scheme[SchemeScratchNorm][ColFloat].pixel);
    else if (c->scratchkey != 0)
        XSetWindowBorder(dpy, c->win, scheme[SchemeScratchNorm][ColBorder].pixel);
    else if (c->isfloating)
        XSetWindowBorder(dpy, c->win, scheme[SchemeNorm][ColFloat].pixel);
    else
        XSetWindowBorder(dpy, c->win, scheme[SchemeNorm][ColBorder].pixel);
    #else
    if (c->isfloating)
        XSetWindowBorder(dpy, c->win, scheme[SchemeNorm][ColFloat].pixel);
    else
        XSetWindowBorder(dpy, c->win, scheme[SchemeNorm][ColBorder].pixel);
    #endif // RENAMED_SCRATCHPADS_PATCH
    #endif // BAR_FLEXWINTITLE_PATCH
    if (setfocus) {
        XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime);
        XDeleteProperty(dpy, root, netatom[NetActiveWindow]);
    }
}

void
unmanage(Client *c, int destroyed)
{
    Monitor *m;
    #if SWITCHTAG_PATCH
    unsigned int switchtag = c->switchtag;
    #endif // SWITCHTAG_PATCH
    XWindowChanges wc;
    #if XKB_PATCH
    XkbInfo *xkb;
    #endif // XKB_PATCH

    #if ZOOMSWAP_PATCH
    /* Make sure to clear any previous zoom references to the client being removed. */
    #if PERTAG_PATCH
    int i;
    for (m = mons; m; m = m->next) {
        for (i = 0; i <= NUMTAGS; i++) {
            if (m->pertag->prevzooms[i] == c) {
                m->pertag->prevzooms[i] = NULL;
            }
        }
    }
    #else
    if (c == prevzoom)
        prevzoom = NULL;
    #endif // PERTAG_PATCH
    #endif // ZOOMSWAP_PATCH
    m = c->mon;

    if (c->swallowing) {
        unswallow(c);
        return;
    }

    Client *s = swallowingclient(c->win);
    if (s) {
        free(s->swallowing);
        s->swallowing = NULL;
        arrange(m);
        focus(NULL);
        return;
    }

    detach(c);
    detachstack(c);
    freeicon(c);
    if (!destroyed) {
        wc.border_width = c->oldbw;
        XGrabServer(dpy); /* avoid race conditions */
        XSetErrorHandler(xerrordummy);
        XSelectInput(dpy, c->win, NoEventMask);
        XConfigureWindow(dpy, c->win, CWBorderWidth, &wc); /* restore border */
        XUngrabButton(dpy, AnyButton, AnyModifier, c->win);
        setclientstate(c, WithdrawnState);
        XSync(dpy, False);
        XSetErrorHandler(xerror);
        XUngrabServer(dpy);
    }
    #if XKB_PATCH
    else {
        xkb = findxkb(c->win);
        if (xkb != NULL) {
            if (xkb->prev)
                xkb->prev->next = xkb->next;
            if (xkb->next)
                xkb->next->prev = xkb->prev;
            free(xkb);
        }
    }
    #endif // XKB_PATCH

    #if SCRATCHPAD_ALT_1_PATCH
    if (scratchpad_last_showed == c)
        scratchpad_last_showed = NULL;
    #endif // SCRATCHPAD_ALT_1_PATCH

    free(c);
    if (s)
        return;
    arrange(m);
    focus(NULL);
    updateclientlist();
    #if SWITCHTAG_PATCH
    if (switchtag && ((switchtag & TAGMASK) != selmon->tagset[selmon->seltags]))
        view(&((Arg) { .ui = switchtag }));
    #endif // SWITCHTAG_PATCH
}

void unmap_notify(XEvent *e)
{
    Client *c;

    XUnmapEvent *ev = &e->xunmap;

    if ((c = win_to_client(ev->window))) {
        if (ev->send_event)
            setclientstate(c, WithdrawnState);
        else
            unmanage(c, 0);
    }
    else if (showsystray && (c = wintosystrayicon(ev->window))) {
        /* KLUDGE! sometimes icons occasionally unmap their windows, but do
         * _not_ destroy them. We map those windows back */
        XMapRaised(dpy, c->win);
        removesystrayicon(c);
        drawbarwin(systray->bar);
    }
}

void updatebars(void)
{
    Bar *bar;
    Monitor *m;
    XSetWindowAttributes wa = {
        .override_redirect = True,
        .background_pixel = 0,
        .border_pixel = 0,
        .colormap = cmap,

        #if BAR_TAGPREVIEW_PATCH
        .event_mask = ButtonPressMask|ExposureMask|PointerMotionMask
        #else
        .event_mask = ButtonPressMask|ExposureMask
        #endif // BAR_TAGPREVIEW_PATCH
    };
    XClassHint ch = {"dwm", "dwm"};
    for (m = mons; m; m = m->next) {
        for (bar = m->bar; bar; bar = bar->next) {
            if (bar->external)
                continue;
            if (!bar->win) {
                bar->win = XCreateWindow(dpy, root, bar->bx, bar->by, bar->bw, bar->bh, 0, depth,
                                          InputOutput, visual,
                                          CWOverrideRedirect|CWBackPixel|CWBorderPixel|CWColormap|CWEventMask, &wa);
                XDefineCursor(dpy, bar->win, cursor[CurNormal]->cursor);
                XMapRaised(dpy, bar->win);
                XSetClassHint(dpy, bar->win, &ch);
            }
        }
    }
}

void update_bar_pos(Monitor *m)
{
    m->wx = m->mx;
    m->wy = m->my;
    m->ww = m->mw;
    m->wh = m->mh;

    Bar* bar = NULL;
    int y_pad = 0;
    int x_pad = 0;

    for (bar = m->bar; bar; bar = bar->next) {
        bar->bx = m->wx + x_pad;
        bar->bw = m->ww - 2 * x_pad;
    }

    for (bar = m->bar; bar; bar = bar->next) {
        if (!m->showbar || !bar->showbar) {
            bar->by = -bar->bh - y_pad;
        }
    }

    if (!m->showbar) {
        return;
    }

    for (bar = m->bar; bar; bar = bar->next) {
        if (!bar->showbar) {
            continue;
        }

        if (bar->topbar) {
            m->wy = m->wy + bar->bh + y_pad;
        }

        m->wh -= y_pad + bar->bh;
        bar->by = (bar->topbar ? m->wy - bar->bh : m->wy + m->wh);
    }
}

void updateclientlist()
{
    Client *c;
    Monitor *m;

    XDeleteProperty(dpy, root, netatom[NetClientList]);
    for (m = mons; m; m = m->next)
        for (c = m->clients; c; c = c->next)
            XChangeProperty(dpy, root, netatom[NetClientList],
                XA_WINDOW, 32, PropModeAppend,
                (unsigned char *) &(c->win), 1);

    XDeleteProperty(dpy, root, netatom[NetClientListStacking]);
    for (m = mons; m; m = m->next)
        for (c = m->stack; c; c = c->snext)
            XChangeProperty(dpy, root, netatom[NetClientListStacking],
                XA_WINDOW, 32, PropModeAppend,
                (unsigned char *) &(c->win), 1);
}

int update_geom(void)
{
    int dirty = 0;

    // FIXME:// 拔掉第二屏后，第二屏上的窗口不会自动回到第一屏
    if (XineramaIsActive(dpy)) {
        int i, j, n, nn;
        Client* c = NULL;
        Monitor* m = NULL;
        XineramaScreenInfo* info = XineramaQueryScreens(dpy, &nn);                      // nn 表示 屏幕 数量
        XineramaScreenInfo* unique = NULL;
        if (G_UNLIKELY(!info)) {
            LOG_ERROR("XineramaQueryScreens failed!");
        }

        LOG_INFO("screen number: %d", nn);

        for (n = 0, m = mons; m; m = m->next, n++);                                     // n 表示屏幕数量
        unique = ecalloc(nn, sizeof(XineramaScreenInfo));
        for (i = 0, j = 0; i < nn; i++) {
            if (is_unique_geom (unique, j, &info[i])) {
                memcpy (&unique[j++], &info[i], sizeof (XineramaScreenInfo));
            }
        }
        XFree(info);
        nn = j;                                                                         // j 表示去掉重复屏幕后的屏幕数量
        sort_screens(unique, nn);                                                       // 按屏幕 (x, y) 座标位置排序

        for (i = n; i < nn; i++) {
            for (m = mons; m && m->next; m = m->next);                                  // FIXME:// 链表尾插，此处数据结构应该被替换
            if (m) {
                m->next = create_mon();
            }
            else {
                mons = create_mon();
            }
        }

        for (i = 0, m = mons; i < nn && m; m = m->next, i++) {                          //
            if ((i >= n) || (unique[i].x_org != m->mx) || (unique[i].y_org != m->my) || (unique[i].width != m->mw) || (unique[i].height != m->mh)) {
                dirty = 1;
                m->num = i;
                m->mx = m->wx = unique[i].x_org;
                m->my = m->wy = unique[i].y_org;
                m->mw = m->ww = unique[i].width;
                m->mh = m->wh = unique[i].height;
                update_bar_pos (m);                                                     // 根据是否显示 bar 来确定屏幕的显示区域 和 bar 的显示区域
            }
        }

        /* removed monitors if n > nn */
        for (int i = nn; i < n; i++) {
            for (m = mons; m && m->next; m = m->next);
            while ((c = m->clients)) {
                dirty = 1;
                m->clients = c->next;
                detachstack(c);
                c->mon = mons;
                attach(c);
                attachstack(c);
            }

            if (m == selmon) {
                selmon = mons;
            }
            cleanup_mon(m);
        }
        free(unique);
    }
    else {
        /* default monitor setup */
        if (!mons) {
            mons = create_mon();
        }
        if (mons->mw != sw || mons->mh != sh) {
            dirty = 1;
            mons->mw = mons->ww = sw;
            mons->mh = mons->wh = sh;
            update_bar_pos(mons);
        }
    }

    if (dirty) {
        selmon = mons;
        selmon = win_to_mon(root);
    }

    return dirty;
}

void
updatenumlockmask(void)
{
    unsigned int i, j;
    XModifierKeymap *modmap;

    numlockmask = 0;
    modmap = XGetModifierMapping(dpy);
    for (i = 0; i < 8; i++)
        for (j = 0; j < modmap->max_keypermod; j++)
            if (modmap->modifiermap[i * modmap->max_keypermod + j]
                == XKeysymToKeycode(dpy, XK_Num_Lock))
                numlockmask = (1 << i);
    XFreeModifiermap(modmap);
}

void
updatesizehints(Client *c)
{
    long msize;
    XSizeHints size;

    if (!XGetWMNormalHints(dpy, c->win, &size, &msize))
        /* size is uninitialized, ensure that size.flags aren't used */
        #if SIZEHINTS_PATCH || SIZEHINTS_RULED_PATCH || SIZEHINTS_ISFREESIZE_PATCH
        size.flags = 0;
        #else
        size.flags = PSize;
        #endif // SIZEHINTS_PATCH | SIZEHINTS_RULED_PATCH | SIZEHINTS_ISFREESIZE_PATCH
    if (size.flags & PBaseSize) {
        c->basew = size.base_width;
        c->baseh = size.base_height;
    } else if (size.flags & PMinSize) {
        c->basew = size.min_width;
        c->baseh = size.min_height;
    } else
        c->basew = c->baseh = 0;
    if (size.flags & PResizeInc) {
        c->incw = size.width_inc;
        c->inch = size.height_inc;
    } else
        c->incw = c->inch = 0;
    if (size.flags & PMaxSize) {
        c->maxw = size.max_width;
        c->maxh = size.max_height;
    } else
        c->maxw = c->maxh = 0;
    if (size.flags & PMinSize) {
        c->minw = size.min_width;
        c->minh = size.min_height;
    } else if (size.flags & PBaseSize) {
        c->minw = size.base_width;
        c->minh = size.base_height;
    } else
        c->minw = c->minh = 0;
    if (size.flags & PAspect) {
        c->mina = (float)size.min_aspect.y / size.min_aspect.x;
        c->maxa = (float)size.max_aspect.x / size.max_aspect.y;
    } else
        c->maxa = c->mina = 0.0;
    #if SIZEHINTS_PATCH || SIZEHINTS_RULED_PATCH || SIZEHINTS_ISFREESIZE_PATCH
    #if SIZEHINTS_ISFREESIZE_PATCH
    if ((size.flags & PSize) && c->isfreesize)
    #else
    if (size.flags & PSize)
    #endif // SIZEHINTS_ISFREESIZE_PATCH
    {
        c->basew = size.base_width;
        c->baseh = size.base_height;
        c->isfloating = 1;
    }
    #if SIZEHINTS_RULED_PATCH
    checkfloatingrules(c);
    #endif // SIZEHINTS_RULED_PATCH
    #endif // SIZEHINTS_PATCH
    c->isfixed = (c->maxw && c->maxh && c->maxw == c->minw && c->maxh == c->minh);
    c->hintsvalid = 1;
}

/**
 * @brief
 *  状态栏（显示时间）
 */
void updatestatus(void)
{
    gettextprop(root, gsCustomStatusAtoms[CUSTOM_STATUS_TIME], stext, sizeof(stext));

    for (Monitor* m = mons; m; m = m->next) {
        drawbar (m);
    }
}

void
updatetitle(Client *c)
{
    #if IPC_PATCH
    char oldname[sizeof(c->name)];
    strcpy(oldname, c->name);
    #endif // IPC_PATCH

    if (!gettextprop(c->win, netatom[NetWMName], c->name, sizeof c->name))
        gettextprop(c->win, XA_WM_NAME, c->name, sizeof c->name);
    if (c->name[0] == '\0') /* hack to mark broken clients */
        strcpy(c->name, broken);

    #if IPC_PATCH
    for (Monitor *m = mons; m; m = m->next) {
        if (m->sel == c && strcmp(oldname, c->name) != 0)
            ipc_focused_title_change_event(m->num, c->win, oldname, c->name);
    }
    #endif // IPC_PATCH
}

void
updatewmhints(Client *c)
{
    XWMHints *wmh;

    if ((wmh = XGetWMHints(dpy, c->win))) {
        if (c == selmon->sel && wmh->flags & XUrgencyHint) {
            wmh->flags &= ~XUrgencyHint;
            XSetWMHints(dpy, c->win, wmh);
        } else
            c->isurgent = (wmh->flags & XUrgencyHint) ? 1 : 0;
        if (c->isurgent) {
            if (c->isfloating)
                XSetWindowBorder(dpy, c->win, scheme[SchemeUrg][ColFloat].pixel);
            else
                XSetWindowBorder(dpy, c->win, scheme[SchemeUrg][ColBorder].pixel);
        }
        if (wmh->flags & InputHint)
            c->neverfocus = !wmh->input;
        else
            c->neverfocus = 0;
        XFree(wmh);
    }
}

void
view(const Arg *arg)
{
    #if TAGSYNC_PATCH
    Monitor *origselmon = selmon;
    for (selmon = mons; selmon; selmon = selmon->next) {
    #endif // TAGSYNC_PATCH
    #if EMPTYVIEW_PATCH
    if (arg->ui && (arg->ui & TAGMASK) == selmon->tagset[selmon->seltags])
    #else
    if ((arg->ui & TAGMASK) == selmon->tagset[selmon->seltags])
    #endif // EMPTYVIEW_PATCH
    {
        #if TOGGLETAG_PATCH
        view(&((Arg) { .ui = 0 }));
        #endif // TOGGLETAG_PATCH
        return;
    }
    #if BAR_TAGPREVIEW_PATCH
    tagpreviewswitchtag();
    #endif // BAR_TAGPREVIEW_PATCH
    #if VIEW_HISTORY_PATCH
    if (!arg->ui) {
        selmon->seltags += 1;
        if (selmon->seltags == LENGTH(selmon->tagset))
            selmon->seltags = 0;
    } else {
        if (selmon->seltags == 0)
            selmon->seltags = LENGTH(selmon->tagset) - 1;
        else
            selmon->seltags -= 1;
    }
    #else
    selmon->seltags ^= 1; /* toggle sel tagset */
    #endif // VIEW_HISTORY_PATCH
    if (arg->ui & TAGMASK)
        selmon->tagset[selmon->seltags] = arg->ui & TAGMASK;
    #if PERTAG_PATCH
    pertagview(arg);
    #endif // PERTAG_PATCH
    #if TAGSYNC_PATCH
    }
    selmon = origselmon;
    #endif // TAGSYNC_PATCH
    #if TAGSYNC_PATCH
    arrange(NULL);
    #else
    arrange(selmon);
    #endif // TAGSYNC_PATCH
    focus(NULL);
    updatecurrentdesktop();
}

Client* win_to_client(Window w)
{
    Client *c;
    Monitor *m;

    for (m = mons; m; m = m->next) {
        for (c = m->clients; c; c = c->next) {
            if (c->win == w) {
                return c;
            }
        }
    }

    return NULL;
}

Monitor* win_to_mon(Window w)
{
    int x, y;
    Client *c;
    Monitor *m;
    Bar *bar;

    if (w == root && getrootptr(&x, &y)) {
        return recttomon(x, y, 1, 1);
    }

    for (m = mons; m; m = m->next) {
        for (bar = m->bar; bar; bar = bar->next) {
            if (w == bar->win) {
                return m;
            }
        }
    }

    if ((c = win_to_client(w))) {
        return c->mon;
    }

    return selmon;
}

/* There's no way to check accesses to destroyed windows, thus those cases are
 * ignored (especially on UnmapNotify's). Other types of errors call Xlibs
 * default error handler, which may call exit. */
int xerror(Display *dpy, XErrorEvent *ee)
{
    if (ee->error_code == BadWindow
        || (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch)
        || (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable)
        || (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable)
        || (ee->request_code == X_PolySegment && ee->error_code == BadDrawable)
        || (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch)
        || (ee->request_code == X_GrabButton && ee->error_code == BadAccess)
        || (ee->request_code == X_GrabKey && ee->error_code == BadAccess)
        || (ee->request_code == X_CopyArea && ee->error_code == BadDrawable)) {

        return 0;
    }

    LOG_WARNING("request code=%d, error code=%d", ee->request_code, ee->error_code);

    return xerrorxlib(dpy, ee); /* may call exit */
}

int
xerrordummy(Display *dpy, XErrorEvent *ee)
{
    return 0;
}

int xerror_start(Display *dpy, XErrorEvent *ee)
{
    LOG_DIE(APP_NAME": another window manager is already running");

    return -1;
}

void
zoom(const Arg *arg)
{
    Client *c = selmon->sel;
    #if FOCUSMASTER_RETURN_PATCH && ZOOMSWAP_PATCH
    int i;
    #endif // FOCUSMASTER_RETURN_PATCH
    if (arg && arg->v)
        c = (Client*)arg->v;
    if (!c)
        return;
    #if ZOOMSWAP_PATCH
    Client *at = NULL, *cold, *cprevious = NULL, *p;
    #endif // ZOOMSWAP_PATCH

    #if ZOOMFLOATING_PATCH
    if (c && c->isfloating)
        togglefloating(&((Arg) { .v = c }));
    #endif // ZOOMFLOATING_PATCH

    #if SWAPFOCUS_PATCH && PERTAG_PATCH
    c->mon->pertag->prevclient[c->mon->pertag->curtag] = nexttiled(c->mon->clients);
    #endif // SWAPFOCUS_PATCH

    if (!c->mon->lt[c->mon->sellt]->arrange || !c || c->isfloating)
        return;

    #if ZOOMSWAP_PATCH
    if (c == nexttiled(c->mon->clients)) {
        #if PERTAG_PATCH
        p = c->mon->pertag->prevzooms[c->mon->pertag->curtag];
        #else
        p = prevzoom;
        #endif // PERTAG_PATCH
        at = findbefore(p);
        if (at)
            cprevious = nexttiled(at->next);
        if (!cprevious || cprevious != p) {
            #if PERTAG_PATCH
            c->mon->pertag->prevzooms[c->mon->pertag->curtag] = NULL;
            #else
            prevzoom = NULL;
            #endif // PERTAG_PATCH
            #if SWAPFOCUS_PATCH && PERTAG_PATCH
            if (!c || !(c = c->mon->pertag->prevclient[c->mon->pertag->curtag] = nexttiled(c->next)))
            #else
            if (!c || !(c = nexttiled(c->next)))
            #endif // SWAPFOCUS_PATCH
                return;
        } else
            #if SWAPFOCUS_PATCH && PERTAG_PATCH
            c = c->mon->pertag->prevclient[c->mon->pertag->curtag] = cprevious;
            #else
            c = cprevious;
            #endif // SWAPFOCUS_PATCH
    }

    cold = nexttiled(c->mon->clients);
    if (c != cold && !at)
        at = findbefore(c);
    #if FOCUSMASTER_RETURN_PATCH
    for (i = 0; !(selmon->tagset[selmon->seltags] & 1 << i); i++);
    i++;

    c->mon->tagmarked[i] = cold;
    #endif // FOCUSMASTER_RETURN_PATCH
    detach(c);
    attach(c);
    /* swap windows instead of pushing the previous one down */
    if (c != cold && at) {
        #if PERTAG_PATCH
        c->mon->pertag->prevzooms[c->mon->pertag->curtag] = cold;
        #else
        prevzoom = cold;
        #endif // PERTAG_PATCH
        if (cold && at != cold) {
            detach(cold);
            cold->next = at->next;
            at->next = cold;
        }
    }
    focus(c);
    arrange(c->mon);
    #elif SWAPFOCUS_PATCH && PERTAG_PATCH
    if (c == nexttiled(c->mon->clients) && !(c = c->mon->pertag->prevclient[c->mon->pertag->curtag] = nexttiled(c->next)))
        return;
    pop(c);
    #else
    if (c == nexttiled(selmon->clients) && !(c = nexttiled(c->next)))
        return;
    pop(c);
    #endif // ZOOMSWAP_PATCH
}

int main(int argc, char *argv[])
{
    if (argc == 2 && !strcmp("-v", argv[1])) {
        printf (APP_NAME ": " VERSION "\n");
        return 0;
    }
    else if (argc != 1) {
        printf ("usage" APP_NAME "[-v]\n");
        return 0;
    }

    if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) {
        printf("warning: no locale support\n");
    }

    g_log_set_writer_func (log_handler, NULL, NULL);
    LOG_INFO(APP_NAME " start...")

    if (!(dpy = XOpenDisplay(NULL))) {
        LOG_DIE("graceful-wm: cannot open display");
    }

    {
        XFixesSetClientDisconnectMode (dpy, XFixesClientDisconnectFlagTerminate);
    }

    if (!(xcon = XGetXCBConnection(dpy))) {
        LOG_DIE("graceful-wm: cannot get xcb connection");
    }

    check_other_wm();

    setup();
#ifdef __OpenBSD__
    #if SWALLOW_PATCH
    if (pledge("stdio rpath proc exec ps", NULL) == -1)
    #else
    if (pledge("stdio rpath proc exec", NULL) == -1)
    #endif // SWALLOW_PATCH
        die("pledge");
#endif /* __OpenBSD__ */
    scan();

    run();
    cleanup();
    XCloseDisplay(dpy);

    LOG_INFO(APP_NAME " stopped!");

    return EXIT_SUCCESS;
}

